RIFE logo
This page last changed on Sep 14, 2006 by alexafshar.

Best Practices

In this section you'll find a listing of "Best Practices" from RIFE developers. The information listed below should help new and old developers design and implement their RIFE application in the most efficent and stable manner possible. Readers might also find the tips and tricks section useful.

Site Design

Sessions

Manually storing temporary application state in sessions is disfavored in RIFE web applications (see why). A standard RIFE application indicates what the values are that should be passed around and RIFE does what is needed by adding the required information to the query string or forms. This doesn't mean that the state itself has to be stored in there. The state storage is configurable and it's easy to store data in a session if you need to hide sensitive data, have large objects, etc. (click here).

If you do need to access the HttpSession, then you still can do so, but you'll have to explicitly tell RIFE that you want access to the RAW servlet API:

setProhibitRawAccess(false);
HttpSession session = getHttpServletRequest().getSession();

The reason why this is shielded, is that it's possible to break RIFE's web engine features when the HttpServletRequest and HttpServletResponse are directly accessed. Additionally, people might continue to rely on their existing habits with regards to working with parameters and URLs if this is immediately available. The additional step at least indicates that RIFE has other mechanisms that might be better suited.

Configuration

Annotations

If you are using Java 5, and you don't plan to make frequent changes to the structure of your site, you might find annotations easier to maintain than the XML configuration. All the element-level configuration you can do in the XML configuration can also be done using annotations, with the additional benefit that in many cases you can use references to your element implementations' java.lang.Class objects rather than using element IDs. This is nice because you get IDE support: autocompletion, auto-updating of references if you refactor, etc.

Here is a simple example of an embedded element that refers to two other elements, one in a subsite. The site XML can look like this:

Sample site definition
<?xml version="1.0" encoding="UTF-8">
<!DOCTYPE site SYSTEM "/dtd/site.dtd">

<site>
    <!-- This is the embedded element that shows an entry in a product list. -->
    <element id="ProductListEntry" implementation="com.foo.ProductListEntry" />

    <!-- This page shows details for a single product. -->
    <element id="ShowProduct" implementation="com.foo.ShowProduct" url="show" />

    <!-- An authentication element for controlling access to administrative functions. -->
    <element id="AuthAdmin" extends="rife/authenticated/database.xml">
        ... template_name, etc. -- see the user's manual ...
    </element>

    <!-- And some elements in the subsite, defined elsewhere. -->
    <subsite id="Admin" file="admin.xml" urlprefix="admin" />
</site>

We declare the URL in the XML configuration because it is generally a piece of information that's not used by the actual element implementations, and so that the XML configuration can be quickly examined to look up which element handles a given URL.

The embedded element looks like this (not a complete implementation):

ProductListEntry.java
package com.foo;

@Elem(
   // Need an explicit empty URL in annotations of embedded elements
   // or RIFE will use the class name as the URL. (For non-embedded
   // elements, we just need an empty @Elem annotation since we
   // declare URLs in the XML configuration.)
   url = ""
)
public class ProductListEntry extends Element {
   // This defines an exit (the name of which is the value of the field
   // the annotation is associated with) and a flowlink leading from
   // that exit. This flowlink leads to the EditProduct element in the
   // admin-only section of the site, so we need to tell RIFE which
   // subsite to look in (since there could be instances of EditProduct
   // in multiple subsites).
   //
   // We pass one piece of data to the EditProduct element; by declaring
   // the datalink inside the flowlink, we don't have to repeat the 
   // destination element ID/class.
   @FlowlinkExitField(destClass = EditProduct.class,
           destClassIdPrefix = "^Admin",
           datalinks = {
               @Datalink(srcOutput="productId", destInput="productId")
           })
   public static final String EXIT_EDIT_PRODUCT = "editProduct";

   // Another exit that goes to an element in the same site, so we
   // don't need the prefix here.
   @FlowlinkExitField(destClass = ShowProduct.class,
           datalinks = {
               @Datalink(srcOutput="productId", destInput="productId")
           })
   public static final String EXIT_SHOW_PRODUCT = "showProduct";

   // Our output is supplied by a getter method. You can also declare
   // outputs by name in the class-level annotation using the
   // @Output(name="whatever") annotation, but in many cases using
   // a property results in less code clutter since you don't need to
   // explicitly populate the outputs in your handler methods.
   // Output properties can be beans or primitive types.
   @OutputProperty
   public int getProductId() {
       if (_product != null)
           return _product.getId();
       return 0;
   }

   public void processElement() {
       ...
       // At some point the element processing decides to hand off to
       // another element. By using a defined constant rather than
       // just specifying an exit name inline, we get compile-time
       // detection of typos in exit names.
       exit(EXIT_SHOW_PRODUCT);
   }

Templates

Authentication

Database

Adding Constraints

Make sure when you add a constraint that it exactly matches the field name of the Bean. Example:

Simple.java

package org.rifers.beans;

public class Simple{
	private String someValue = 0;

	public Simple(){}
    
        ...
}

SimpleMetaData.java

package org.rifers.beans;

public class SimpleMetaData extends MetaData {
	public void activateMetaData() {
		addConstraint(new ConstrainedProperty("someValue").identifier(true).unique(true));
		//not addConstraint(new ConstrainedProperty("somevalue").identifier(true).unique(true));
	}
}

Schema Creation

Most RIFE developers let RIFE handle their schema generation through the use of an InstallParticipant, MetaData merging and a series of Manager classes. The InstallParticipant creates the DB schema on the first run of the RIFE project, the MetaData classes add constraints to the POJO Beans, like unique(), identifier(), maxLength(), etc. and the Manager classes build your DAO layer. Most project will have a MetaData and Manager class associated with each Bean.

Simple.java

package org.rifers.beans;

public class Simple{
	private int id = -1;
	private String value = 0;

	public Simple(){}
    
        ...
}

SimpleMetaData.java

package org.rifers.beans;

public class SimpleMetaData extends MetaData {
	public void activateMetaData() {
		addConstraint(new ConstrainedProperty("id").identifier(true).unique(true));
		addConstraint(new ConstrainedProperty("value").notNull(true).maxLength(256));
	}
}

SimpleManager.java

package org.rifers.beans.dao;

public class SimpleManager {
	private static final Class BEAN_CLASS = Simple.class;
	
	public void install(Datasource datasource)
	{		
		GenericQueryManager manager =
			  GenericQueryManagerFactory.getInstance(datasource, BEAN_CLASS);
		
		manager.install();
	}
}

InstallParticipant.java

package org.rifers.participants;

public class InstallParticipant extends BlockingParticipant {
	protected void initialize() {
		Datasource datasource = Datasources.getRepInstance()
                                        .getDatasource(Config.getRepInstance().getString("datasource_name"));
		try{
			SimpleManager simpleBeanMananger = new SimpleManager();
			simpleBeanManager.install(datasource);			
		}catch(Throwable e){
			Logger.getLogger(ProjectSettingsHelper.getProjectName())
                              .warning("The database structure couldn't be installed, it probably already exists.");
			Logger.getLogger(ProjectSettingsHelper.getProjectName())
                              .warning(ExceptionUtils.getExceptionStackTraceMessages(e));
		}
	}
}

Config-Base.xml

<config>
	<param name="datasource_name">postgresql</param> <!-- or whatever you use, but you should use PGSQL -->
	<param name="database_name">DB_NAME_HERE</param>
	<param name="database_user">USER_NAME_HERE</param>
	<param name="database_password">PASSWORD_NAME_HERE</param>
</config>

Participants.xml

Add this line:
<participant>org.rifers.participants.InstallParticipant</participant>

When you run your RIFE setup the InstallParticipant gets called, then creates a Datasource connection, passes that off to the SimpleBeanManager and tells it to install the Simple Bean to your database. The MetaData class is "merged" with your Simple Bean to instruct the Generic Query Manager (GQM) on which constraints to add to the database. Finally, the GQM uses reflection on the Simple Bean and determines which fields to create inside the DB and which constraints to add. The code above would generate a table "simple" in the database, with all of the proper constraints including an auto-incrementing.

Content Management System (CMS)

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