RIFE logo
This page last changed on May 20, 2009 by taxaudit.


RIFE provides a Content Management Framework that is geared towards the process of storing, loading, validating, formatting, transforming, versioning, retrieving and streaming of various content types. Currently the emphasis has been placed on providing features that are needed when putting content data into a back-end repository and getting the data back out. The actual management of the content once it is stored has not yet been implemented in an elaborate manner, but this will be done in a future version (plus the integration and implementation of JSR-170 JCRQL to search for particular content).

These notes will only briefly cover the main features of the CMF, which is quite comprehensive.

Overview

The impatient reader may choose to skip down to the example. There you'll find the logic that is needed to store news items. Each news item has validated XHTML content and an optional image that is automatically scaled into two formats during storage and afterwards streamed directly to the browser. You'll see that very little code is needed to provide these functionalities.

All CMF content is stored in repositories that must have unique names. The installation of a content manager creates an initial default repository. If others are needed, they must be created explicitly.
All content is identified by a unique location. The location is formatted like this:

repository:path

If the repository: prefix is omitted, the content will be stored in the 'default' repository. The path should start with a slash, which makes it 'absolute'; this is completely analogous to unix file system paths.

Content is stored through an instance of ContentManager and RIFE includes implementations for the storage in PostgreSQL, MySQL, Oracle, McKoiSQL, HypersonicSQL, Cloudscape and Firebird databases. Below are a few terms and their definitions in the context of the CMF:

ContentManager The main interface that handles all high-level content operations (create, get, delete, etc.). All code behind this interface is implementation dependent. RIFE currently provides an implementation that is backed by a database, but it's totally possible to provide alternative implementations on top of WebDAV or JSR-170 for instance.
ContentStore An interface to handle the actual storage of content data. There are multiple content stores available, each responsible for handling certain types of content data. Examples are text data, image data, and raw data.
ContentDataUser When content data is retrieved from a content store, a concrete implementation of this abstract class will be given a handle to the actual content data, valid for a limited period of time. This is important since some content data (such as streamed binary data) is volatile and never fully loaded in memory.
Attribute This provides additional information about how the content data is to be formatted. For example, for images this can be a width or a height, in order to ensure that images are scaled before being stored into the content store.
Property This provides information about the formatted content data as it is actually stored in the content store. Even with no attributes defined, content data can still have properties. For instance, an image still has a width that will be detected and stored for later usage.
MimeType All content is tagged with a mime type that indicates how it will be further processed. There is one special mime type, RAW, which causes the content to be stored as-is without any manipulation.
Formatter This uses a Loader to get a typed representation of the raw content data, then formats it according to any defined attributes, transforms it, obtains the formatted data's properties, and converts it back to raw content.
Loader This makes a best-effort attempt to load raw data into a generic typed object, and collects any associated error messages. A loader will typically delegate the actual loading to external libraries when they are available. Examples of those libraries are the JDK XML parser with XHTML DTDs, Jimi, JAI, ImageJ, ...
Transformer This can transform the generic typed object of content data that has been created by the loader into another object of the same type. For example, to perform image manipulations or compositions. See Transformers for an example.

[top]

Workflow

The database implementations have the following workflow:

Store content

  1. Select a suitable content store based on the content's mime type
  2. Look up the content repository
  3. Store the content info (metadata) for the specified location (i.e. path)
  4. Store the content attributes
  5. Store the content data into the content store:
    1. Obtain a formatter for the content data
    2. Use the formatter to format the data:
      1. Load the raw content data into a newly-created Java object of an appropriate type (such as java.lang.String for text data and java.awt.Image for image data)
      2. Report any loading errors
      3. Format the content according to any attributes provided
      4. Transform the formatted content if a transformer has been provided
      5. Obtain the properties of the final content
      6. Convert the Java object back to raw content data
    3. Store the formatted content data into the content store
  6. Store the formatted content data's properties

Use content data

  1. Retrieve the content info for the specified location (i.e. path)
  2. Select a suitable content store based on the mime type that is provided by the content info
  3. Make the content data available via from content store:
    1. Look up the content data
    2. Call a ContentDataUser instance with the raw content data that was retrieved, and make it available to the caller

[top]

ContentQueryManager

To simplify working with content, a new query manager has been developed, called ContentQueryManager, which extends GenericQueryManager. This query manager class works hand-in-hand with CMF-specific constraints. These additional constraints allow you to provide CMF-related metadata for bean properties while still having access to all regular constraints.

The most important additional constraint is 'mimeType'. Setting this constraint directs RIFE to delegate the handling of that property's data to the CMF instead of storing it as a regular column in a database table. The property content location (i.e. its full path) is generated automatically based on the bean class name, the instance's identifier value (i.e. the primary key used by GenericQueryManager), and the property name. So for example, if you have an instance of the NewsItem class whose identifier is 23, then the full path that is generated for a property named text is '/newsitem/23/text'. Note that this always specifies the most recent version of the property, but that older versions are also available from the content store.

To make it easy to handle file uploads (which are quite common when uploading content), the ConstrainedProperty class now has an additional file() constraint that will automatically create a file parameter for a submission bean and populate the property with the uploaded data after a submission.

Before being able to use the CMF and a ContentQueryManager, you must install both of them, as in this example:

Datasource ds = Datasources.getRepInstance().getDatasource("datasource");
DatabaseContentFactory.getInstance(ds).install();
new ContentQueryManager(ds, NewsItem.class).install();

Example: News Item

Here is a short example that demonstrates how to integrate a user-defined compound document called a "news item" with the CMF. A single image associated with the news item will be defined to have two sizes for delivery, "small" and "medium". When it is stored to the CMF, the image is rescaled in accordance with these size constraints, and it can also be automatically converted from other formats (such as PNG). These variants can also be streamed directly to a browser, based on which access path (small or medium) is used.

the java bean NewsItem.java:

package com.mypackage;

import com.uwyn.rife.cmf.*;
import com.uwyn.rife.site.*;

public class NewsItem extends MetaData
{
    private int       mId = -1;
    private String    mTitle = null;
    private String    mText = null;
    private byte[]    mNewsImage = null;
    
    protected void activateMetaData()
    {
        addConstraint(new ConstrainedProperty("title")
                            .notNull(true)
                            .maxLength(90));
        // specify a type for automatic validation 
        addConstraint(new ConstrainedProperty("text")
                            .mimeType(MimeType.APPLICATION_XHTML)
                            .autoRetrieved(true)
                            .fragment(true)
                            .notNull(true)
                            .notEmpty(true));
        addConstraint(new ConstrainedProperty("newsImage")
                            .persistent(false)
                            .file(true));
        // specify types for content delivery and sizes for automatic rescaling
        addConstraint(new ConstrainedProperty("imageSmall")
                            .mimeType(MimeType.IMAGE_JPEG)
                            .contentAttribute("width", 70)
                            .editable(false));
        addConstraint(new ConstrainedProperty("imageMedium")
                            .mimeType(MimeType.IMAGE_JPEG)
                            .contentAttribute("width", 120)
                            .editable(false));
        addConstraint(new ConstrainedProperty("id")
                            .editable(false)
                            .saved(false));
    }
    
    public void   setId(int id)                      { mId = id; }
    public int    getId()                            { return mId; }
    public void   setTitle(String title)             { mTitle = title; }
    public String getTitle()                         { return mTitle; }
    public void   setText(String text)               { mText = text; }
    public String getText()                          { return mText; }

    // As always in RIFE, a setter/getter pair defines a property.
    // In this case, imageSmall and imageMedium are both "virtual"
    // properties, which reference the mNewsImage property, but 
    // are delivered after transformations, i.e. they are resized 
    // to the dimensions specified in the above Constraints. 
    public void   setNewsImage(byte[] newsImage)     { mNewsImage = newsImage; }
    public byte[] getNewsImage()                     { return mNewsImage; }
    public void   setImageSmall(byte[] imageSmall)   { }  // dummy setter 
    public byte[] getImageSmall()                    { return mNewsImage; }
    public void   setImageMedium(byte[] imageMedium) { }  // dummy setter 
    public byte[] getImageMedium()                   { return mNewsImage; }
}

the element declaration add_news.xml:

<element implementation="com.mypackage.elements.AddNews">
    <submission name="add">
        <bean classname="com.mypackage.NewsItem"/>
    </submission>
</element>

the element implementation AddNews.java:

package com.mypackage.elements;

public class AddNews extends Element
{
    private Template mTemplate;
    
    public void initialize()
    {
        mTemplate = getHtmlTemplate("add_news");
    }
    
    public void processElement()
    {
        print(mTemplate);
    }
    
    public void doAdd()
    {
        NewsItem newsitem = (NewsItem)getSubmissionBean(NewsItem.class);
        
        if (!newsitem.validate())
        {
            generateForm(mTemplate, newsitem);
            print(mTemplate);
            return;
        }
        
        Datasource ds = Datasources.getRepInstance().getDatasource("datasource");
        ContentQueryManager manager = new ContentQueryManager(ds, NewsItem.class);
        manager.save(newsitem);
        
        mTemplate.setBlock("content", "content_success");
        print(mTemplate);
    }
}

This code takes care of all the logic to store a new news item with two variants of an optional image (small and medium) that are automatically scaled and stored as JPEGs in the CMF together with the article's text. The text will be validated and only valid XHTML Transitional 1.0 will be accepted. Depending upon which image handling libraries are available in the classpath of the application, the image when it is uploaded can be in one of many other formats which can be automatically converted to JPEG before storage into the CMF. Without any external libraries only the JDK standard types are supported: JPEG, GIF and PNG.

To display the news item in a browser, some of the work is done automatically for you for the text property since it has the autoRetrieved constraint. This will automatically fetch the content from the ContentStore and fill it into the bean as if it were stored in the actual news item table in a dedicated column. For images, you cannot use this since you have to generate some HTML code which fetches the images themselves from a dedicated URL (the src attribute of the image tag).

The CMF provides an element implementation that streams content data directly to a browser according to the path info that the element receives. This path info will be used as the content path when it is retrieved from the ContentManager. You must include both the element and an exit to the element. You can for example integrate this element in your site structure like this:

<globalexit name="SERVE_CONTENT" destid="SERVE_CONTENT"/>

<element id="SERVE_CONTENT" file="rife/cmf/serve_content.xml" url="/content/*">
    <property name="datasource"><datasource><config param="DATASOURCE"/></datasource></property>
</element>

The 'datasource' property has to contain the name of the datasource to use, which in this case is fetched from the config.

Once this element is in place, you just need a template to display the news item, like this for example:

<div><!--V 'title'/--></div>
<div><!--V 'image'--><!--/V--></div>
<div><!--V 'content'/--></div>

The following element implementation will display the news item according the id that has to be provided as an input:

package com.mypackage.elements;

import com.uwyn.rife.cmf.*;
import com.uwyn.rife.cmf.dam.*;
import com.uwyn.rife.database.*;
import com.uwyn.rife.config.*;

public class ViewNews extends NewsCommon
{
    public void processElement()
    {
        Template t = getHtmlTemplate("view_news");

        int id = getInputInt("id");

        Datasource ds = Datasources.getRepInstance().getDatasource(Config.getRepInstance().getString("DATASOURCE"));
        ContentQueryManager manager = new ContentQueryManager(ds, NewsItem.class);

        NewsItem newsitem = (NewsItem)manager.restore(id);
        if (null == newsitem)
        {
            return;
        }
        
        t.setBean(newsitem);
        if (manager.hasContent(newsitem, "imageMedium"))
        {
            //params for getContentForHtml are: the bean, the content property, an element, and the content exit
            t.setValue("image", manager.getContentForHtml(newsitem, "imageMedium",
                                    this, "SERVE_CONTENT"));
        }
    }
}

[top]
http://google.com

Document generated by Confluence on Oct 19, 2010 14:57