Images and Attachments


Table of Contents

1. Introduction
2. Images and ImageServlet
2.1. Image conversion
2.2. Image height and width
2.3. Attachments
3. XSLT
4. More about the builders, 'servlet builders'.
5. More about the implementation of the servlets.
5.1. Introduction, servdb
5.2. Bridge servlet, handle servlet, image servlet
5.3. Configuration

This document describes how you have to deal with images and attachments in MMBase. We will start with a simple description of what these things are for MMBase and how you can use them, and will end with some implementation details an MMBase developer might need to know.

Lets start with the most simple thing, which is needed in almost every MMBase installation. Images are in MMBase just another form of content, so they can be stored in a builder (a.k.a. 'node manager'). This builder is named `images' in the MMBase distribution, and can be installed by deploying the resources' application. Normally the image itself will be stored as a blob in the database, though MMBase can be configured to do it differently. Of course normally the image-builder will also feature the meta information about the image.

But there is more to it then this. There must e.g. be a way to present the image to the visitor of your web site. For the meta information about the image this is of course no problem, you can do that just as you would with other MMBase node fields. If you use the MMBase taglib you would for example list the related images like this:

<mm:relatednodes type="images">
   <img src="????" 
	height="<mm:field name="height" />" width="<mm:field name="width" />" 
	alt="<mm:field name="title" />" /><mm:field name="description" /><br />
</mm:relatednodes>

As you know, you cannot use the image blob on you HTML page, but you need an URL which presents it. So that is why in the previous example I just typed '????'. Let's explain now how this issue is solved in MMBase, how MMBase can provide this '????'.

The answer is that we have created a servlet for you to do this. This servlet takes a node-number as its argument. It can then retrieve the node from MMBase and searches for a field 'handle' in this node, and the content of this is used to response the http request. Other fields of the node, such as 'itype' and 'title' are used to decorate the http response with the right headers, such as the content-type (which for an image is something else then for an HTML-page) and the content-disposition header (to supply a nice suggestion for a file name).

In 'web.xml' it is configured which URLs correspond to this servlet. The suggestion of the distro is '/img.db' which means that then the image of node 123 is available on the url: http://yourhost/context/img.db?123. I like to map the image servlet to "/images/*", and then the image 123 is available on http://yourhost/context/images/123, which I find to look a bit better. 'yourhost' is the name of your web server and 'context' is the application context you have started MMBase in (it can be empty, then MMBase is running in the 'root' context, but this is not generally true).

Well, you know that you do not need to know explicitly the host name of your server when you make your html page, because you can refer to the image relatively in the 'img' tag. It is also not very convenient to use an url like <img src="/context/images/123" /> because 'context' can change (you can move the page to another MMBase instance in another context), and even the 'images/' part can vary between MMBase installations, because it can be configured to which URL the image-servlet is mapped by the application server.

That's why in MMBase there are functions to retrieve this information for you automatically. If you use the MMBase Taglib there is the 'image' tag for this:

<mm:relatednodes type="images">
   <img src="<mm:image />"
	height="<mm:field name="height" />" width="<mm:field name="width" />" 
	alt="<mm:field name="title" />" /><mm:field name="description" /><br />
</mm:relatednodes>
So, this image tag finds out for you in which context your MMBase is running and to which URL's the servlet is mapped and it then produces the right URL.

If you do not use the MMBase taglib, then the same thing can be done. Here is an example using the 'bridge'.

NodeListIterator i = node.getRelatedNodes("images").nodeListIterator();
while(i.hasNext()) {
  Node image = i.nextNode();
  out.println("<img src=\"" + image.getFunctionValue("servletpath", null) + image getNumber() + "\"
      alt=\"" + image.getStringValue("title") + "\"/>" + image.getStringValue("description") +
      "/><br />");
}
This does about the same as the above taglib example.

Note

The taglib example is really a bit more advanced. For example it tries to add user information to the URL if necessary, and it tries to put a file name in the URL if that is possible for the servlet (which helps some browsers). I advice to use the image-tag if you can.

As mentioned, images can be converted to other images. This is a very useful feature, because you often need smaller versions of an image, or perhaps a version of the image which is re-styled in another way to fit in the look and feel of your web site. These things can be automated by MMBase to a large extend, which makes the life for content editors easier, because for images it suffices to provide one (high quality) image, and MMBase can handle the conversion of this image to versions needed on the web site.

Converted images are stored in a special builder named 'icaches' (cached images), so they are much the same as the source images, the only difference being that they are automatically generated.

So for the image servlet is does not make much difference if it has to serve a 'cached' image or an 'original' image.

But how does MMBase decide which 'icache' nodes must be created, and how can you know which are available, and to which original image they belong?

The answer is that there is a function in the images builder which accepts 'conversion commands', and returns the node number of the 'icache' node which is associated with this original image and those commands. If such a node cannot be found, it will be created.

The 'conversion commands' are passed in a string referred to as 'template'. This string contains the commands separated by the + character. The syntax of the individual commands is very similar to the command line options of the 'convert' command of ImageMagick, because that program is commonly plugged in to perform the actual conversion.

A very simple example of such a conversion template is "geometry(100x200)", which is the command to resize the image to fit into a rectangle of 100 x 200 pixels. So, the number of the icache node which is a conversion by this template of the image presented by the Node 'image' can be requested from the bridge like this:

Node image;
...
List arguments = new ArrayList();
argument.add("geometry(100x200)");
int iCacheNodeNumber = image.getFunctionValue("cache", arguments).intValue();
Of course this node number can then be fed to the image servlet, on precisely the same way as you would do for a normal image.

In the MMBase taglib this functionality is also present in the before-mentioned image tag, and can be triggered by using the 'template' attribute. Showing the '100x200' version of the related images therefore would look like this:

<mm:relatednodes type="images">
   <img src="<mm:image template="geometry(100x200)" />" alt="<mm:field name="title" />" /><mm:field name="description" /><br />
</mm:relatednodes>

Note

MMBase provides an abbreviation for this vastly used command 'geometry' which is simply 's' (for size), so normally you would encounter:

   <img src="<mm:image template="s(100x200)" />" alt="<mm:field name="title" />" /><mm:field name="description" /><br />
Which is precisely the same, only shorter.

Lets give also an example of a template which consists of more than one command, e.g. a 'size' command plus a rotate command:

<mm:image template="s(100x200)+r(90)" />
The order of the commands can be significant, and you can supply the same command more then once.

TODO: overview of MMBase specific commands. Fonts and Texts. JAI implementation.

We refer to the documentation of ImageMagick for a complete description of all commands. Also in the image tag entry of the MMBase taglib reference guide there are given some examples.

Until now we've said nothing about the 'height' and 'width' of images. In HTML you actually need to specify those in the height and width attributes of the img tag. Since MMBase-1.8 height and width are fields of the two image-builders (images and icaches). These fields are automaticly filled by MMBase, so you should never have to worry about it.

The image-tag however, provided only the URL of a cached image. So in the previous chapter the issue was ignored. It would be fixed if you have somehow access to the actual 'icaches' node. This is this indeed possible (using a 'nodefunction' tag), but this is not very convenient and there for there are 2 alternatives in the mm:image tag.

The first alternative is the 'mode' attribute on mm:image. When its value is 'attributes' it will not only produce the URL for the image, but it will produce the 3 relevant attributes for a XHTML img-tag. Like this:

<mm:relatednodes type="images">
   <img <mm:image mode="attributes" template="s(100)" />
	alt="<mm:field name="title" />" /><mm:field name="description" /><br />
</mm:relatednodes>
So then you don't have to specify the 'src' attribute itself either, because it is provided by the image-tag now.

You can also specify 'img' as a value for the 'mode' of the image-tag, which will, of course, produce an entire img-tag. You would probably end up with something like:

<mm:relatednodes type="images">
   <mm:image mode="img" template="s(100)" /><mm:field name="description" /><br />
</mm:relatednodes>

The second alternative is that the mm:image tag produces a 'dimension' variable which can be accessed using JSP2's Expression Language. This variable is available in the body of the mm:image tag. The URL is available as ${_} (as with any 'Writer' tag). One could end up with something like:

<mm:relatednodes type="images" id="image" >
   <mm:image template="s(100)">
     <img src="${_}" width="${dimension.width}" height="${dimension.height}" title="${image.title}" alt="${image.title}"  /><mm:field name="description" /><br />
   </mm:image>
</mm:relatednodes>

In MMBase-1.8, the creating of the converted image is actually 'lazy', so the icaches object is created without the byte-array first, which will only be filled on actual request of the client. This however has little influence on the height and width of the icaches object, because those are 'predicted', and are therefore available immediately, also when the actual conversion has not yet taken place.

Sometimes you want to generate output using XSLT. This happens for example in the Editwizard implementation and you can also do it on a JSP page using the `mm:formatter' tag. Generally, as in these two examples, the source XML would describe the fields of the MMBase nodes only, like e.g. <field name="number">123</field>. So, the same problem as in JSP/taglib arises, namely, how to convert this to an actual URL for the image or the attachment? The mm:image tag is not available in XSL.

One could start with simply creating URL's using the number. You only need to know what the URLs of the servlets are then. These could possibly be passed as a parameter into the XSL, or could simply be hard-coded in your custom XSL.

The problem would then be solved at least for attachments. For images, the problem is again more complex, because generally you would not include also the 'icaches' node in the XML.

The solution which was used in Editwizards and also in the default XSLTs used by mm:formatter is to use Xalan extensions, which actually boils down to the possibility to call java functions from inside XSL. The draw-back of course is that this creates a Xalan dependency (or at least on similar products, e.g. also the XSLT processor of Resin supports this), and worse, that it would become quite hard to perform the XSLT not on the server itself.

For this to work the utility class org.mmbase.bridge.util.xml.NodeFunction was created, which can be installed in your XSL like this:

<xsl:stylesheet  
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:node="org.mmbase.bridge.util.xml.NodeFunction"
    ....

      
and then be used like this:
<img src ="{node:function($cloud, ./o:field[@name='number'], 'servletpath(,cache(s(100x100&gt;)))')}" 
      
      
We refer to the javadoc of
NodeFunction for more information on how to use it.

It has become clear that the images and attachments builders are more than simple dumb MMBase builders ('Dummy). They do provide extra functionality like 'servletpath' and 'cache' functions. In the builder xml of Images and Attachments you can see this because another class is configured (by the 'class' tag (or 'classfile' in older XML's))

The general feature of images and attachments nodes is that they need to be associated with a Servlet. Therefore the abstract builder 'ServletBuilder' was introduced which provides the general functionality for these kind of things. It provides the mechanism to determine the right URL to the right Servlet for a certain 'function' (ImageServlet provides the function 'images', AttachmentServlet provided 'attachments').

The images, icaches and attachments servlets are all extensions of this abstract servlet builder. I suggest that if you need to create a builder which is associated to a servlet too, you do it like this.


This is part of the MMBase documentation.

For questions and remarks about this documentation mail to: [email protected]