Blogs : Latest entries
|
|
|
This is primarily a bug-fix release. We believe most important bugs have been identified and been resolved, which justifies a new major release. Below are the highlights of this release.
Many bugfixes and improvementsThe following sections received a number of bugfixes and were improved:
Additional constraintsTwo new
constraints were added to
Easy way to retrieve a
|
|
I'll be doing a presentation on web continuations with RIFE at Fosdem this weekend. It was fun doing this with Keynote under MacOSX. Really great results and very easy to use. For those that want a sneak peak, head over to the download area where you can find the keynote files (1.7MB), a high-quality quicktime movie (22.7MB), a medium-quality quicktime movie (1.4MB) and a PDF file (0.7MB). |
|
Unless major problems are found, this is the last of the unstable releases. The focus of the next release will be the addition of more documentation, more examples, and the creation of a Wafer weblog implementation. Below are the highlights of this release.
Much better support for web continuationsDifferent continuation handling models Since RIFE tries its best to make the end-user's
experience as comfortable as possible, continuations have changed to fully
support back-button presses in a browser. So someone can take different
execution paths in an application, go back, take another path and even
later resume from earlier paused location. To make this work, RIFE needs
to clone the context of the executing code each time a continuation is
resumed, thus creating a totally independent context that doesn't
interfere with any previous ones. The downside is that to be able to do
this, all variables in the scope need to be cloned too. For most commonly
used JDK types, this has been automated and a lot of RIFE's classes are
implementing Cloneable now too. However, you need to be aware that this is
a requirement for any of the classes that you write yourself or that you
use from an external library. To make your life easier, you can use the
There are several options to
solve this. First, you have to be aware of the fact that the scope for the
continuations context consists out of the local variables of the
public class Continuation extends Element
{
public void processElement()
{
Template template = getHtmlTemplate("mytemplate");
String result = getUncloneableResult();
template.setValue("result", result);
print(template);
pause();
}
private String getUncloneableResult()
{
UncloneableType type = new UncloneableType();
return type.getResult();
}
}
Your other option is to indicate to RIFE that it shouldn't clone the
continuation context at all, but always continue to use the same one. This
largely removes any restrictions on the usable types, but it also prevents
an end-user from back-tracking and taking another execution path. Only the
last-used continuation will remain active and RIFE will enforce this by
changing the continuation IDs at each request and invalidating old IDs.
While this might seem like a nice quick solution if you run into trouble,
you should think very carefully before using it since it results in making
the back-button of the browser (or Safari's snapback function) throw the
user back to the beginning of the element. A non-negligible benefit of
this behaviour however, is that continuations will use much less
resources. That being said, you have two options to indicate to RIFE that
you want this behaviour for an element. Either you override the
public class Continuation extends Element
{
public void processElement()
{
Template template = getHtmlTemplate("mytemplate");
UncloneableType type = new UncloneableType();
template.setValue("result", type.getResult());
print(template);
pause();
}
public boolean cloneContinuations()
{
return false;
}
}
Support for inter-element continuations (call/answer) Continuations are now not limited anymore to one
element. It's now also possible to activate an exit and pause the
execution of the calling element. When the target element stops
processing, the execution of the calling element will automatically
resumed. You can do this very simply by using
For example, consider the following element
implementations that are linked together through a flowlink that starts at
the " public class Call extends Element
{
public void processElement()
{
print("before");
call("callexit");
print("after");
}
}
public class CallTarget extends Element
{
public void processElement()
{
print("-target-");
}
}
The resulting output will be: before-target-after While this may seem very trivial and look like a regular method call, that is exactly its power. Consider that it is completely decoupled and adaptable through the site structure, that it will be remembered even when the target element uses a pause() call to suspend the execution while gathering user info and that the target element element could be written in one of the scripting languages like Groovy. One typical use for
inter-element continuations is the display of intermediate pages like
dialogs or help pages without losing track of the main execution flow. To
make this even more comfortable, it's possible to forcibly return control
to the calling element and to pass a value along by using the
For example, the following element implementations are again
linked together through a flowlink that connects the " public class Delete extends Element
{
public void processElement()
{
Template template = getHtmlTemplate("pub.home");
print(template);
pause();
if (hasSubmission("delete"))
{
Boolean answer = (Boolean)call("dialog");
if (answer.booleanValue())
{
print("deleted");
}
else
{
print("not deleted");
}
}
}
}
public class Dialog extends Element
{
public void processElement()
{
Template template = getHtmlTemplate("pub.dialog");
print(template);
pause();
if (hasSubmission("yes"))
{
answer(new Boolean(true));
}
else if (hasSubmission("no"))
{
answer(new Boolean(false));
}
}
}Added customization support to generic query managerIn this release the GenericQueryManager recieved a major boost which greatly enhanced its flexibility. Deletions and restorations can now be customized with restricted queries. These queries have exactly the same interface as the regular Select, and Delete classes, accept that harmful methods have been removed. This leaves only methods that will not harm the integrity of the query and will work on full beans. Using the new customizations are quite easy, for example using a RestoreQuery: public class RestoreQueryTest extends Element
{
public void processElement()
{
// ...
GenericQueryManager articles = GenericQueryManagerFactory.getInstance(datasource, Article.class);
RestoreQuery query = articles.getRestoreQuery();
query
.where("title", "=", "Article Title");
List articleList = articles.restore(query);
// ...
}
}Another newly extended interface is DeleteQuery. An example of its usage: public class DeleteQueryTest extends Element
{
public void processElement()
{
// ...
GenericQueryManager articles = GenericQueryManagerFactory.getInstance(datasource, Article.class);
DeleteQuery query = articles.getDeleteQuery();
query
.where("title", "=", "Article Title");
articles.delete(query);
// ...
}
}When using subselects and other more advanced features you may need to get the name of the table that the GenericQueryManager uses to store its data. The new method getTable(), which returns a String, provides this functionality. Fully integrated data type meta-data facility through constraintsMany parts of an application benefit a lot from an easy way to obtain the type, the accepted limits, and the behavioural patterns of a data entity. In RIFE this has now been centralized into a bean. You have the possibility to add a collection of constraints to its properties and retrieve this information later on. This neatly centralizes the addition of this meta-data. What are constraints The main purpose of a constraint is to alter the default behaviour of a data type and to clearly set the accepted limits. The meta-data that's provided through constraints can be used elsewhere to gather more information about how to correctly integrate the indicated data limits. For example, a constraint specifies that a certain text's length is limited to 30 characters, parts of the system can query this information and act accordingly:
Several types of constraints will be available, but currently
only constrained bean properties are supported through the
Constrained properties A The property name refers to the actual name of the bean
property. However, this sometimes doesn't correspond to its conceptual
usage. It can be handy to receive constraint violation reports with
another conceptual name: the subject name. Notice that this corresponds to
the subject that is used in a It's possible to add constraints to a ConstrainedProperty instance through regular setters, but chainable setters are also available to make it possible to easily define a series of constraints, for example: ConstrainedProperty constrained = new ConstrainedProperty("password");
constrained
.maxLength(8)
.notNull(true);Constrained properties are typically added to a
public class Credentials extends Validation
{
private String mLogin = null;
private String mPassword = null;
private String mLanguage = null;
public Credentials()
{
addConstraint(new ConstrainedProperty("login").maxLength(6).notNull(true));
addConstraint(new ConstrainedProperty("password").maxLength(8).notNull(true));
addConstraint(new ConstrainedProperty("language").notNull(true));
}
public void setLogin(String login) { mLogin = login; }
public String getLogin() { return mLogin; }
public void setPassword(String password) { mPassword = password; }
public String getPassword() { return mPassword; }
public void setLanguage(String language) { mLanguage = language; }
public String getLanguage() { return mLanguage; }
}It's also possible however to add constraints to a single bean instance whenever they cannot be determined beforehand. These are then dynamic constraints which can be populated at runtime, for example: Credentials credentials = new Credentials();
credentials.addConstraint(new ConstrainedProperty("language").inList(new String[] {"nl", "fr", "en"}));
To retrieve constrained properties after they have been added, you
can use the Totally rewritten validation systemThe previous validation system was much too rudimentary, requiring too much code to be written and too much to be done manually by the developer. It has therefore been completely rewritten, becoming very flexible, powerful and unintrusive. Incremental validation Validation is bound to subjects that have distinct
names. Each subject corresponds to a different variable, for example a
property of a bean. When a subject is found to be invalid, a corresponding
instance of
A
Since it is
possible that subjects generate multiple Automatic rule creation from constraints Validation is now able to automatically create and
add validation rules according to constraints. The example
Validation groups Since it's
quite common to validate different collections of rules one after the
other (in a wizard or a multi-paged form for instance) you can now define
validation groups. They are identified by a unique name and it is possible
to validate only the rules that are included in a particular group. To
define a validation group you simply use the public class Credentials extends Validation
{
public Credentials()
{
addGroup("step1")
.addConstraint(new ConstrainedProperty("login").maxLength(6).notNull(true))
.addConstraint(new ConstrainedProperty("password").maxLength(8).notNull(true));
addGroup("step2")
.addConstraint(new ConstrainedProperty("language").notNull(true));
}
// the properties code is trivial, see above
}To actually validate against the groups you have two options. You start from scratch and validate only the rules of the group like this: Credentials credentials = new Credentials();
credentials.validateGroup("step1");
// do something if errors occurred
credentials.resetValidation();
credentials.validateGroup("step2");
// do something if errors occurredYou can also validate normally and focus down to only the subjects that are validated by the groups, like this: Credentials credentials = new Credentials();
credentials.validate();
credentials.focusGroup("step1");
// do something if errors occurred
credentials.resetValidation();
credentials.validate();
credentials.focusGroup("step3");
// do something if errors occurredError message generation, decoration, specification, positioning The previous
Fallback message positioning and decoration The template value in which validation error messages are collected by default is now the following: <!--V 'ERRORS:*'--><!--/V--> You
can set a custom message in the area with the
<!--B 'ERRORS:*'--><p><!--V 'ERRORS'/--></p><!--/B--> The
If you want to decorate each error message individually, you should specify it like this: <!--B 'ERRORMESSAGE:*'--><b><!--V 'ERRORMESSAGE'/--></b><br /><!--/B--> The
Considering all the above template directives, the following code: ValidationBuilderXhtml builder = new ValidationBuilderXhtml(); builder.setFallbackErrorArea(template, "my message"); will generate the following result: <p><b>my message</b><br /></p> If you generate a collection of validation errors: ValidationBuilderXhtml builder = new ValidationBuilderXhtml(); builder.generateValidationErrors(template, errors, null); the following result could be generated with the same template directives: <p><b>Invalid login.</b><br /><b>MANDATORY:password</b><br /><b>WRONGLENGTH:language</b><br /></p> Selective error area positioning and decoration Often you want to position
error messages near the specific form fields they describe, and a global
error area is not appropriate. You can do this by adding the subject name
to the <!--V 'ERRORS:language'--><!--/V--> Dependent on your design, you might even want to display the error messages of several subjects in a certain location. This is also possible, like this: <!--V 'ERRORS:login,password'--><!--/V--> Note that it's possible to use the same subject name in several value IDs. To select in which value a message will appear, RIFE uses a fallback mechanism that selects values according to the number of matching erroneous subjects. For each subject it goes over all the value IDs that contain it, the order of traversal is from the value with the most subject names to the one with the least. The first value ID in which all specified subject names are invalid will be used. For example, consider the following error area values: <!--V 'ERRORS:login,password,language'--><!--/V--> <!--V 'ERRORS:login,password'--><!--/V--> <!--V 'ERRORS:login'--><!--/V--> <!--V 'ERRORS:password'--><!--/V--> <!--V 'ERRORS:*'--><!--/V--> Now, examine the following situations where each time different subjects are invalid:
On its own this mechanism isn't very handy, but it becomes interesting when you combine it with the selective decoration for these error areas. It is based on the same principle, but uses an inverted mechanism to determine which block to use. This means that it will first start with the blocks that specify the least amount of subject names and end with those that specify the most. As soon as all subject names of a used error area are present in the decoration block, the block will be used. For example, consider the following decoration blocks together with the error areas from above: <!--B 'ERRORS:login,password'--><!--V 'ERRORS'/--><!--/V--> <!--B 'ERRORS:login'--><!--V 'ERRORS'/--><!--/V--> <!--B 'ERRORS:password'--><!--V 'ERRORS'/--><!--/V--> <!--B 'ERRORS:*'--><!--V 'ERRORS'/--><!--/V--> Now, examine the following situations where each time another error area value is used:
You now probably ask yourself why you would ever use something like this. The typical usage is when you have a design that changes according to the error messages that are displayed. For example, you have a firstname and a lastname field in different columns on the same table row. You want the messages for each field to be displayed above it and when they both are invalid, you just the whole row above. Below is an example template excerpt that demonstrates this: <!--B 'ERRORS:firstname,lastname'-->
<tr class="error_messages">
<td colspan="2"><!--V 'ERRORS'/--></td>
</tr>
<!--/B-->
<!--B 'ERRORS:firstname'-->
<tr>
<td class="error_messages"><!--V 'ERRORS'/--></td>
<td> </td>
</tr>
<!--/B-->
<!--B 'ERRORS:lastname'-->
<tr>
<td> </td>
<td class="error_messages"><!--V 'ERRORS'/--></td>
</tr>
<!--/B-->
<table cellspacing="0" cellpadding="5" border="0">
<tr>
<td><em>Firstname</em></td>
<td><em>Lastname</em></td>
</tr>
<!--V 'ERRORS:firstname,lastname'/-->
<!--V 'ERRORS:firstname'/-->
<!--V 'ERRORS:lastname'/-->
<tr>
<td><input type="text" name="firstname" size="30" /></td>
<td><input type="text" name="lastname" size="30" /></td>
</tr>Selective error message decoration Instead of
using the same decoration for all error messages, you can specify
different ones according to the erroneous subjects. The syntax is similar
to what is used for the selective error area decoration, but you use the
For example, consider the following error message decorations: <!--B 'ERRORMESSAGE:*'--><!--V 'ERRORMESSAGE'/--><br /><!--/B--> <!--B 'ERRORMESSAGE:login,password'--><p><!--V 'ERRORMESSAGE'/--></p><!--/B--> <!--B 'ERRORMESSAGE:login,language'--><div><!--V 'ERRORMESSAGE'/--></div><!--/B-->Now, examine the following situations where each time another error message subject is displayed:
Error message content specification As before, the
content of the displayed error messages is determined according to the
identifier and subject name of the error messages. For example, the
validation error with the identifier '
If no block could be found to obtain the error message content
from, ' Error marking Whenever validation errors
occur, it's nice to be able to clearly mark the parts of the page where
the errors have to be corrected. RIFE supports this by looking for
' For example: <!--B 'MARK:ERROR'--> class="error_mark"<!--/B--> <label[!V 'MARK:login'][!/V] for="login">login</label> <div[!V 'MARK:login'/]><input type="text" name="login" id="login" size="18" /></div> When the markings are generated: ValidationBuilderXhtml builder = new ValidationBuilderXhtml(); builder.generateErrorMarkings(template, errors, null); the result is the following when an error occurs for the login subject: <label class="error_mark" for="login">login</label> <div class="error_mark"><input type="text" name="login" size="18" /></div> and if the subject is valid, this will be generated: <label for="login">login</label> <div><input type="text" name="login" size="18" /></div> Selective mark positioning Like for the error area and error message
values, you can declare several subjects after the Alternative markings Instead of using the content of the
' <!--B 'MARK:ERROR:MYALT'--> class="error_alternative"<!--/B--> <label[!V 'MARK:MYALT:password'][!/V] for="password">password</label> <div[!V 'MARK:MYALT:password'/]><input type="password" name="password" id="password" size="18" /></div> This allows you to use, for example, different marking layouts for different forms on a same page or even for different sections of the same form. Automated form buildingHTML
forms can now be automatically generated from beans. This is fully
integrated with the constraints mechanism and the validation facility. To
generate a form, you simply have to call the Empty forms
can also be generated from bean classes through the
Forms are constructed from fields and it's those fields that you have to specify in your template through specific value tags. According to each value ID prefix, RIFE knows which type of field to generate. The prefix is, as usual, followed by the property name. The following fields are supported: <!--V 'FORM:INPUT:property'/--> Generates a regular text field. <!--V 'FORM:SECRET:property'/--> Generates a text field where the entered text is not displayed. <!--V 'FORM:TEXTAREA:property'/--> Generates a text area for the input of multi-lined text. <!--V 'FORM:RADIO:property'/--> Generates radio buttons for the selection of one value from a set of possibilities. <!--V 'FORM:CHECKBOX:property'/--> Generates checkbox buttons for the selection of several values from a set of possibilities. It can also be used with just one value as a boolean switch. <!--V 'FORM:SELECT:property'/--> Generates a selection list. <!--V 'FORM:HIDDEN:property'/--> Generates a hidden field for implicit data submission. Collection fields The radio, checkbox and select fields allow the user to
select from a collection of values. However, these values are rarely those
that you want to display to the user in the interface. Most of the time a
more descriptive label is shown for each option so that the interface
becomes less cryptic. You can specify these labels in blocks in the
template, the format of the label block ID is:
' However, before being able
to generate all these options, RIFE has to know what the possible values
are for the field. This is currently only possible through the use of a
For example, consider the following constrained property: bean.addConstraint(
new ConstrainedProperty("colors").inList(new String[] {"black", "red", "blue"}));and the following template excerpt: <!--V 'FORM:SELECT:colors'/--> <!--B 'FORM:LABEL:colors:blue'-->blue spots<!--/B--> <!--B 'FORM:LABEL:colors:red'-->red spots<!--/B--> <!--B 'FORM:LABEL:colors:green'-->green spots<!--/B--> will generate: <select name="colors"> <option value="red">red spots</option> <option value="blue">blue spots</option> <option value="green">green spots</option> </select> It's also possible that your values are dynamically
added to a constrained property and that the labels can't be defined
statically in your template through blocks. You therefore have the option
to add resource bundles to a template instance. When looking for a label,
RIFE will first check if ' For example, consider the same constrained property as
above (whose list could have been dynamically populated) and the following
code that registers a template.addResourceBundle(new ListResourceBundle() {
public Object[][] getContents()
{
return new Object[][] {
{"colors:blue", "blue spots"},
{"colors:red", "red spots"},
{"colors:green", "green spots"}
};
}});with only this line in the template: <!--V 'FORM:SELECT:colors'/--> will generate exactly the same HTML code as above. Displaying values If you're not generating an empty form, RIFE will look at the actual values of your bean's properties and include them in the template generation. Text fields will thus be populated, checkboxes ticked, radio buttons selected, and select options highlighted. This makes it very easy to create forms where the user has to correct invalid data or where existing data can be edited. Custom field attributes While all relevant constraints of the properties will be examined and the corresponding HTML attributes generated, you'll often want to add other attributes that have nothing to do with the limits that are imposed on the data type. This is very easily done, all you have to do is write these custom attributes inside the field value tag as a default value. They will be added as attributes that are generated automatically. For example, consider the following
bean.addConstraint(
new ConstrainedProperty("login").maxLength(8);and the following field value tag: <!--V 'FORM:INPUT:property'>size="10"<!--/V--> they will generate the following HTML: <input type="text" name="login" size="10" maxlength="8" /> Addition of common database query execution patternsThe A collection of convenience methods
have been provided to quickly execute queries in a variety of manners
without having to worry about the logic behind them and without having to
remember to close the queries at the appropriate moment. These methods
optionally interact with the
Lower-level methods are also available for the sake of
repetitive code-reduction. To obtain a prepared statement that corresponds
to a specific SQL command, use the
Finally, You can look at
the javadocs of the Customizing prepared statements With a child
class of For example: DbQueryManager manager = new DbQueryManager(datasource);
Insert insert = new Insert(datasource);
insert.into("person").fieldParameter("name");
final String name = "me";
int count = manager.executeUpdate(insert, new DbPreparedStatementHandler() {
public void setParameters(DbPreparedStatement statement)
{
statement
.setString("name", name);
}
});If you need to customize the entire query execution, you can
override the The Customizing results With a child class of For example: DbQueryManager manager = new DbQueryManager(datasource);
Select select = new Select(datasource);
select
.field("first")
.field("last")
.from("person");
String result = (String)manager.executeQuery(select, new DbResultSetHandler() {
public Object concludeResults(DbResultSet resultset)
throws SQLException
{
return resultset.getString("first")+" "+resultset.getString("last");
}
});The User identification facility built on top of the authenticationIt's becoming quite common in websites to not force users to be authenticated before they are able to access a section. Instead, pages show different content for and offer other functionalities to users that can correctly be identified. An anonymous visitor simply sees alternative content and is probably restricted in the actions that he can perform. RIFE now offers an easy way to develop websites with these functionalities. User identification is tied to authentication and the identity of a user is obtained from the authentication managers that are used by a specific authentication element. Similar to authentication, RIFE uses element inheritance for the identification. An identification element,
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE element SYSTEM "/dtd/element.dtd">
<element extends="rife/authenticated/identified.xml">
<childtrigger name="authid"/>
</element>The child trigger name should be the same as the one
that is used by the authentication element. Most probably this will be
To include an identification element in the site structure,
you have to tell it which authentication element should be used to perform
the identification. This is done by setting the authentication element ID
as the value of the property with the name
For example: <element id="AUTH" file="element/authentication.xml"/>
<element id="IDENTIFIED" file="element/identified.xml">
<property name="authElementId">AUTH</property>
</element>All the elements that depend on the identification
now simply have to inherit from the For example: public class SomeElement extends Element
{
public void processElement()
{
RoleUserIdentity identity = (RoleUserIdentity)getRequestAttribute("identity");
if (null == identity)
{
// perform anonymous logic
}
else
{
// perform identified logic
}
}
}Note that all authentication elements also provide identification functionalities. So if your element already inherits from such an element there is no use to make it inherit also from an identified element. OGNL support in template engineRIFE now contains OGNL support for templates. Contrary to how OGNL is used in other web application frameworks, RIFE doesn't allow it to retrieve values and fill them into the template since that would make templates more active than we like. OGNL is used to provide boolean expressions that will be evaluated at runtime to automatically assign the content of blocks to values. For example: <!--V 'OGNL:name'-->Evaluated to false<!--/V-->
<!--B 'OGNL:name:[[ true ]]'-->
Evaluated to true
<!--/B-->In this example, the expression in between the
[[ and ]] will be evaluated. If the expression returns
For the expressions to be useful, they have to be evaluated against a context. Therefore it's possible to set variables from an element that can be easily accessed from within the OGNL expression. For example: In an element public class OGNLTest extends Element
{
public void processElement()
{
Template template = getHtmlTemplate("test.ognl");
template.setExpressionVar("show_block", "yes");
print(template);
}
}In a template <!--V 'OGNL:name'-->Block is NOT being displayed<!--/V-->
<!--B 'OGNL:name:[[ #show_block == "yes" ]]'-->
Block is being displayed
<!--/B-->Apart from the expression variables, each OGNL expression is evaluation against a current root object whose methods and properties you can access using the regular OGNL syntax. This root object is by default the template instance that you are processing. To make it easy to write expressions against commonly used contexts, RIFE also provides specialized OGNL tags that set different root objects. Currently the OGNL:ROLEUSER and OGNL:CONFIG tags have been provided. OGNL:ROLEUSER The root object will be the RoleUserAttributes
of an identified user and it will be
This specialized OGNL tag makes it very easy to conditionally show parts of an interface according to the credentials of a user. For example: <!--V 'OGNL:ROLEUSER:role1'-->User is not in role "admin"<!--/V-->
<!--V 'OGNL:ROLEUSER:login1'-->User in named "moderator"<!--/V-->
<!--B 'OGNL:ROLEUSER:role1:[[ isInRole("admin") ]]'-->
User is in role "admin"
<!--/B-->
<!--B 'OGNL:ROLEUSER:login1:[[ #login == "moderator" ]]'-->
User is named "moderator"
<!--/B-->OGNL:CONFIG The root object will be the
default configuration instance: For example: <!--V 'OGNL:CONFIG:bool'>DISPLAY_BLOCK is false<!--/V-->
<!--V 'OGNL:CONFIG:string'>STRING_VALUE is 'do not match'<!--/V-->
<!--B 'OGNL:CONFIG:bool:[[ getBool("DISPLAY_BLOCK") ]]-->
DISPLAY_BLOCK is true
<!--/B-->
<!--B 'OGNL:CONFIG:string:[[ getString("STRING_VALUE") != "do not match" ]]-->
STRING_VALUE is not 'do not match'
<!--/B-->Evaluation order OGNL blocks are always evaluated late (i.e. at time of template printing). If there is a need to evaluate early or repetitively, you can use the following three methods: public class OGNLTest extends Element
{
public void processElement()
{
Template template = getHtmlTemplate("test.ognl");
t.evaluateOgnl("ognl_block");
t.evaluateOgnlConfig("ognl_config_block");
evaluateOgnlRoleUser(t, "ognl_role_user_block");
print(t);
}
}Please note that the method to evaluate an OGNL:ROLEUSER block is not a member of Template, but an inherited method of Element since it needs to access the request context. More information on OGNL syntax can be found at the OGNL User's Guide. Support for Groovy as element implementation languageWhen Groovy is present in the classpath of
the web application, it's now possible to implement elements in this very
nice scripting language. The only requirement is that the source files
have to have the For example,
import com.uwyn.rife.engine.Element
class Simple extends Element
{
void processElement()
{
if (hasSubmission("login"))
{
print(getParameter("login")+","+getParameter("password"))
}
else
{
switch (getInput("input1"))
{
case "form":
print(<<<EOS
<html><body>
<form action="${getSubmissionQueryUrl("login")}" method="post">
<input name="login" type="text">
<input name="password" type="password">
<input type="submit">
</form>
</body></html>
EOS)
break
default:
print(getInput("input1")+","+getInput("input2"))
break
}
}
}
}Configurable state storage with support for server-side storage in sessionsThe use of conventional sessions have always been disabled in RIFE and data has always been passed along the datalinks by using the query string or form post parameters. It has been explained in detail why regular sessions are discouraged and how we managed to offer many of their benefits with a minimum of the drawbacks. RIFE already provides facilities to handle the data flow of an application by setting up the required datalinks in the site structure. You're not actually interested in storing data explicitly in the session, you're however interested in the fact that the data and the state isn't transferred through the client-side but remains on the server. Therefore, we added a configurable state storage mechanism and implemented one for the query string and one for the session; others can easily be added later (database, LDAP, ...). To indicate that you want data to be stored elsewhere, you simple declare state boundaries in the site structure. For example: <state store="session">
<element id="SOURCE" file="element/source.xml" url="/source">
<flowlink srcexit="exit1" destid="DESTINATION"/>
<datalink srcoutput="output1" destid="DESTINATION" destinput="input2"/>
<datalink srcoutput="output2" destid="DESTINATION" destinput="input1"/>
</element>
<element id="DESTINATION" file="element/destination.xml" url="/destination"/>
</state>When you generate an URL for this exit you'll not see
query string parameters for the transferred data, but a
The default state store is still in the
Refactored the repository for easy mocking and testingThe repository has been totally refactored to
make it possible to easily plug-in other repository implementations or
other instances. The It's now very easy to create a custom Repository implementation for testing purposes and to plug it in as the default repository. This example below creates a mock repository with a single mock participant to setup some specific configuration settings that might be needed for testing. The following class is all that is needed: public class MockRepository implements Repository
{
class MockParticipant extends SingleObjectParticipant
{
public Object getObject()
{
Config config = new Config();
config.setParameter("key", "value");
return config;
}
}
private Map mParticipants = new HashMap();
public MockRepository()
{
mParticipants.put("ParticipantConfig", new MockParticipant());
}
public boolean hasParticipant(String name)
{
return mParticipants.containsKey(name);
}
public Participant getParticipant(String name)
{
return (Participant)mParticipants.get(name);
}
public Collection getParticipants(String name)
{
return mParticipants.values();
}
public void cleanup() {}
}Whenever you need to use this repository instead of the current default one, you just have to add this line of code: Rep.setDefaultRepository(new MockRepository()); If you want to restore the previously active repository afterwards, you can retrieve it beforehand with Rep.getDefaultRepository(); and set it back later. Added jumpstart distributionRIFE now provides a jumpstart archive that makes it easy for developers to set up a new application quickly and start developing immediately. The jumpstart contains everything to get started and provides some very common structures as a recommendation for the lay-out of a RIFE application. The enclosed readme contains all the information that needed to use the jumpstart. Miscellaneous web engine enhancementsSupport for automatic web application root URL substitution It's often a problem to develop web applications
that are completely relocatable when reusing common template parts. You
can now use the <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type" />
<base href="[!V 'WEBAPP:ROOTURL'/]" />
<title>The title</title>
<link rel="stylesheet" type="text/css" href="style/pub.css" />
</head>
<body><img src="images/rife-logo.png" width="251" height="105" border="0" alt="RIFE logo" /></body>
</html>More information about the HTML Added support for default implementation directory Similar to the default directories that are available
for elements, sites and templates, you can now put element implementations
in an " Automatic element ID generation When you declare an element in the site structure, the definition of the element ID is now not required anymore. If you omit it, RIFE will look at the XML filename of the element declaration and use the base part for the element ID. For example, the following definition: <element file="element/engine/simple.xml" url="/simple"/> is exactly the same as: <element id ="simple" file="element/engine/simple.xml" url="/simple"/> Element instance property definition When you define elements in the site structure, you can set values for named properties that will only be set for that particular element instance. For example: <element id="PROPERTIES1" file="element/engine/properties.xml" url="/properties1">
<property name="property1">property1a</property>
<property name="property2">property2a</property>
</element>
<element id="PROPERTIES2" file="element/engine/properties.xml" url="/properties2">
<property name="property1">property1b</property>
<property name="property3">property3b</property>
</element>where the element is implemented as follows: public class Properties extends Element
{
public void processElement()
{
print("Property 1 = "+getProperty("property1"));
print("Property 2 = "+getProperty("property2"));
print("Property 3 = "+getProperty("property3"));
}
}When you visit the " Property 1 = property1a Property 2 = property2a Property 3 = nul and for the " Property 1 = property1b Property 2 = null Property 3 = property3b This is currently really a "poor-man's" version of IoC where only literal values can be injected. In a later version we are planning to extend this to a much more flexible system. Element implementations through interfaces It's
now also possible to create elements by implementing the
For example: public class SimpleInterface implements ElementAware
{
private ElementSupport mElement = null;
public void noticeElement(ElementSupport element)
{
mElement = element;
}
public void processElement()
{
mElement.print("Just some text "+
mElement.getRemoteAddr()+":"+
mElement.getRemoteHost()+":"+
mElement.getPathInfo());
}
}
Backwards incompatible changesUnfortunately it was not possible to keep everything fully backwards compatible with this release and that's mainly due to the new validation system. Below are the steps you need to undertake to migrate your application to the new release: implements ValidationRule becomes=> extends AbstractValidationRule checkNotNull(int) becomes=> checkNotEmpty(int) RepParticipant becomes=> BlockingParticipant Error message block IDs change from upper-cased property names to lower-cased property names. You should certainly check your login forms since the error block IDs change there too. Of course, it would be even better to migrate all your forms to the new form builder and validation facility. IDENTIFIER:SUBJECT becomes=> IDENTIFIER:subject NOTNUMERIC:SUBJECT becomes=> NOTNUMERIC:subject All validity checks have moved from the
check*() becomes=> ValidityChecks.check*() |
|
Oh my, it's been almost two years since I've updated RelativeLayers. This release just adds support for Safari and to my surprise, this neat newcomer supports everything that's needed to run all features of RelativeLayers without any problems. Apple really did a great job on this browser. Anyway, get the new release here or gather more information at the project's homepage. Have fun! |


