Real POJO support with meta data merging

RIFE now supports an automated method to add meta data to POJO classes without having any additional code nor annotations inside the POJOs themselves.

Consider a class Foo and another class FooMetaData. When FooMetaData implements the MetaDataMerged interface (which is simply a marker), RIFE will instrument Foo and make it implement all the interfaces that FooMetaData implements. Also, when the default constructor of Foo is called, an instance of FooMetaData will be created and stored in a new hidden member variable. The added method implementations simple delegate execution to this instance of FooMetaData. Optionally, FooMetaData can also implement the MetaDataBeanAware interface, in which case the instance of FooMetaData will receive the instance of Foo that it belongs to right after it has been instantiated. Note that the relationship between Foo and FooMetaData is purely name based (the MetaData suffix). RIFE will look up the meta data class through the classpath, which means that it's possible to augment any class, anywhere, in any package, even without having the source code.

Constraints are a typical usage for this feature. Any POJO is now able to have constraints attached to it, without violating the purity of the POJO class implementation itself. For convenience, you can extend the MetaData abstract base class which implements everything you need to easily specify constraints in a meta data class.

For example, the Person class:

publicclassPerson{

privateString firstname;

privateString lastname;

publicvoid setFirstname(String firstname){

this.firstname = firstname;

}

publicString getFirstname(){

return firstname;

}

publicvoid setLastname(String lastname){

this.lastname = lastname;

}

publicString getLastname(){

return lastname;

}

}

and the PersonMetaData class:

publicclassPersonMetaDataextendsMetaData{

publicvoid activateMetaData(){

addConstraint(newConstrainedProperty("firstname")

.maxLength(10)

.notNull(true));

addConstraint(newConstrainedProperty("lastname")

.inList("Smith","Jones","Ronda"));

}

}

As long as RIFE's classloader is active, both will be automatically merged into a new Person class. You can access the added interface methods simply by casting an instance of Person to the appropriate interface name.

For example:

Person person =newPerson();

Validated validated =(Validated)person;

assertFalse(validated.validate());

validated.resetValidation();

person.setFirstname("John");

person.setLastname("Smith");

assertTrue(validated.validate());

Note that this is also implicitly available to RIFE/Crud, which means that you can easily generated CRUD web interfaces for existing models, without having to add constraints to their original implementation.

Experimental Ajax support with DWR integration

The DWR team has worked hard to make their project as modular as possible and to abstract it away as much as possible from the Servlet API. While their effort is still in progress and there hasn't been an official DWR release with these features yet, we already integrated a DWR snapshot with RIFE to provide easy Ajax capabilities will all the power of RIFE's element components. To make this work you have to use the DWR jar file that's shipped with the rife-sumo distribution archive.

Standard DWR Ajax element

RIFE ships with a new standard element declaration that can be accessed through the file "rife/ajax/dwr.xml".

For example:

<elementid="DwrService"file="rife/ajax/dwr.xml"url="dwr/*"/>

That element will by default look for "dwr.xml" in the classpath. This is a standard DWR configuration file and you can find the details about it in their documentation.

Customize the configuration

However, you can also specify a specific DWR configuration file for each Ajax element through the xmlConfiguratorPath property.

For example:

<elementid="DwrDate"file="rife/ajax/dwr.xml"url="dwr/jdate/*">

<propertyname="xmlConfiguratorPath">jdate.xml</property>

<propertyname="debug">true</property>

</element>

Notice the debug property which, when set to true, allows you to access the root URL of the element ("dwr/jdate/" in this case) and get a nifty DWR test page. It's recommended to turn this on during development.

Your element will now expose the methods of the java.util.Date class through Ajax.

Enable Ajax in your HTML

The purpose of DWR is to easily provide remote access to your Java classes and methods through Ajax. However, to access these remoted methods from within your HTML, you have to include certain Javascript files. This is very easy to do with embedded elements.

For example:

<!--V 'ELEMENT:DwrDate'-->

names = JDate

includeUtil = true

<!--/V-->

The default value of this embedded element tag is a property list, where the "name" key specifies a comma separated list that refers to the "javascript" attributes of your DWR "create" tags. The "includeUtil" key indicates whether the general purpose DWR Javascript utilities should be included or not.

The example above will generate the following Javascript inclusions. Note that as with everything in RIFE, when your element or application is relocated, the URLs will automatically adapt accordingly.

Use Ajax in your layout

The only thing that's now left to do is to use the newly available Javascript functions from within your layout.

The following example calls the "init" Javascript function when the page is loaded. This is only needed to setup DWR so that it shows a message while it's loading data asynchronously through Ajax.

The actual magic happens in the "update" function, which is called when the button on the page is clicked. This method uses the Javascript "JDate" class that has been generated by DWR and calls its "toGMTString" method. This method maps directly to the Java implementation on the server. The single argument, "loadinfo" refers to the function that will be called when the Ajax call returns. Here, it simply displays the data in the HTML span tag with the id "reply".

Embedded element priorities

Normally, embedded elements are processed when the template that contains them is instantiated. In most situations this is exactly what you want, however sometimes it's important to be able to defer the processing to the end of your view handling, ie. when the template is printed. So instead of being processed early, those embedded elements will then be processed late.

You simply do this by prefixing the ID of the embedded element in the value tag with a + (for late processing) or - (for early processing).

For example, these embedded elements:

<!--V 'ELEMENT:+PRIORITIES_EMBEDDED_LATE1'/-->

<!--V 'ELEMENT:PRIORITIES_EMBEDDED_NORMAL1'/-->

<!--V 'ELEMENT:+PRIORITIES_EMBEDDED_LATE2'/-->

<!--V 'ELEMENT:PRIORITIES_EMBEDDED_NORMAL2'/-->

<!--V 'ELEMENT:-PRIORITIES_EMBEDDED_EARLY'/-->

Will be processed in the following order:

PRIORITIES_EMBEDDED_EARLY

PRIORITIES_EMBEDDED_NORMAL1

PRIORITIES_EMBEDDED_NORMAL2

PRIORITIES_EMBEDDED_LATE1

PRIORITIES_EMBEDDED_LATE2

Note that you can of course still process embedded elements explicitly with the processEmbeddedElement method. The embedded ements that have already been processed like that will not be automatically re-processed later.

Strict pathinfo mapping

Path info mapping provides a powerful way to easily support structured path info URLs and receive their values as inputs.

By default however, even if none of the mappings matches the provided pathinfo, the element will be executed. No values will have been extracted from the pathinfo and you're responsible for taking the appropriate action yourself. Usually, this is exactly what you want as a fall back. However, sometimes you might want to have elements that will only be processed if at least one of the declared mappings matches the pathinfo. This can be achieve by setting the "pathinfo" attribute of the element tag to "strict".

For example:

<elementid="ShowPaste"implementation="elements.ShowPaste"

url="paste/*"pathinfo="strict">

<pathinfomapping="$key/show/$id(\d+)"/>

<pathinfomapping="show/$id(\d+)"/>

</element>

<elementid="DiffPastes"implementation="elements.DiffPastes"

url="paste/*"pathinfo="strict">

<pathinfomapping="$key/diff/$oldid(\d+)/$id(\d+)"/>

<pathinfomapping="diff/$oldid(\d+)/$id(\d+)"/>

</element>

Both elements above handle the same pathinfo URL. However, only when the "show" keyword is present, the ShowPaste element will be executed and when the "diff" keyword is present the DiffPastes element will be executed.

Flowlink-specific datalinks

Normally you declare flowlinks and datalinks next to each-other and whenever the logic follows a flowlink, the related datalinks are looked up and all the output values will be provided to the target inputs. This can however create problems if you have different flowlinks that go to the same target element, but that should connect different outputs to the same input. You are now able to tie datalinks to a specific flowlink.

For example:

<elementid="ShowPaste"implementation="elements.ShowPaste"

url="paste/*"pathinfo="strict">

<flowlinksrcexit="show current"destid="ShowPaste">

<datalinksrcoutput="currentid"destinput="id"/>

</flowlink>

<flowlinksrcexit="show parent"destid="ShowPaste">

<datalinksrcoutput="parentid"destinput="id"/>

</flowlink>

<flowlinksrcexit="show amendment"destid="ShowPaste">

<datalinksrcoutput="amendmentid"destinput="id"/>

</flowlink>

</element>

In the example above, three flowlinks point to the same ShowPaste element. However, depending on the triggered exit, different entries need to be shown. This means that the IDs of either the current paste, the parent paste or an amendment need to be provided to the "id" input of ShowPaste.

Support for setting reflexive outputs and globalvars across submissions

When your element has outputs that link to inputs of the element itself, or if globalvars are present, you are now able to set an output value before generating the submission URL and parameters. After the submission is sent, that output value will be provided to the input of the element. Of course, as before, all other inputs will automatically preserved during submissions.

This feature allows you to continue to rely on inputs to receive data for your element. However, you now don't have to handle changing input values by adding additional parameters to your submission and adapting your logic to them, you can simply set an output and continue to use the data that the input provides.