Blogs : Archives
|
|
| < JHighlight 1.0 released | TSSJS is gonna rock! > |
|
As a contribution to Simon Brown's series of articles where he compares Java web application frameworks, I created a RIFE version of the read-only blog that currently serves as an example.
I'd like to underline that we focus on maintainability and development comfort. We firmly believe that it's important to have a high-level, self-documenting overview of your web application. This allows teams to work independently and simultaneously on different parts. At any moment they can integrate their work without stepping on each other's toes. This approach led to the creation of a site-structure that handles both the logic flow and the data flow, as a state machine. Any modification in there will correctly propagate throughout the entire framework, reducing repetition and redundancy to a minimum. I will explain more about the site-structure later in this article. Let's dive into the example. Basically, we have an application with three pages. The Home page displays a list of blog entries, which can optionally have an excerpt. When an excerpt is present, a link will be available towards to full entry Detail page. Also, in case a user tries to access an invalid blog entry, a nice Not Found page will be displayed. Abstract base elementEach page in RIFE is implemented through an element. Each of them requires access to a blog service and will print out a template that contains the name and the description of the blog. I created the following abstract base class that will be extended in the concrete elements: blog/AbstractBlogBase.java package blog; import com.uwyn.rife.engine.Element; import com.uwyn.rife.template.Template; import domain.*; public abstract class AbstractBlogBase extends Element { protected BlogService service; public void setBlogService(BlogService service) {this.service = service;} protected Template template; public void setTemplate(Template template) {this.template = template;} public void initialize() { template.setBean(getBlog()); } public Blog getBlog() { return service.getBlog(); } }The setters will be used to inject the objects through inversion of control. The Reusable template blueprintApart from the page-specific content, the layout of each template will be exactly the same. I thus created a common blueprint that will later be included into the other templates. blog/blueprint.html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>${v name/}</title> <base href="${v WEBAPP:ROOTURL/}"/> <link rel="stylesheet" href="style/screen.css" type="text/css" /> </head> <body> <div id="container"> <h1>${v name/}</h1> <h2>${v description/}</h2> ${v page content}${/v} </div> </body> </html>The highlighted tags are values ( Note that RIFE supports many alternative template tag syntaxes, this one is inspired by Velocity, others are for example valid HTML and allow you to preview the file in a regular browser. Home elementThis is the implementation of the home element. It simply iterates over the blog entries and displays them in the template. If the excerpt contains data, it will create a " blog/Home.java package blog; import domain.Blog; import domain.BlogEntry; public class Home extends AbstractBlogBase { public void processElement() { Blog blog = getBlog(); for (BlogEntry entry : blog.getBlogEntries()) { template.setBean(entry); if (entry.getExcerpt() != null) { setOutput("entryId", entry.getId()); setExitQuery(template, "more"); template.setBlock("entry content", "excerpt"); } else { template.setBlock("entry content", "body"); } template.appendBlock("entries", "entry"); } print(template); } }Since the display of each entry will be the same for this element and the next blog/entry.html <div class="blogEntry"> <h3>${v title /}</h3> ${v entry content/} <p>Posted on ${v date/}</p> </div>The template of the home element will now include the blueprint that we created earlier and this snippet. It also introduces a new concept, template blocks ( blog/home.htmlNote that there's a special value tag with the prefix " Detail elementThe detail element receives the ID of the entry that it has to display, it will be injected through the corresponding setter method. When it can't find an existing entry, it simply exits to display the " blog/Detail.java package blog; import domain.BlogEntry; public class Detail extends AbstractBlogBase { private String id; public void setId(String id) { this.id = id;} public void processElement() { BlogEntry entry = service.getBlog().getBlogEntry(id); if (null == entry) exit("not found"); template.setBean(entry); print(template); } }The template of this element contains nothing new and reuses the blueprint and the entry templates again. However, instead of conditionally setting blocks to fill in the " blog/detail.html
NotFound elementThe blog/NotFound.java public class NotFound extends AbstractBlogBase { public void processElement() { setStatus(404); print(template); } }The template is trivial: blog/notfound.html
Meta data through constraintsSimon's domain model was left untouched. However, the application requirements specify richer information about the data model: the This is the meta data class that will be automatically picked up by RIFE and merged into the
package domain; import com.uwyn.rife.rep.Rep; import com.uwyn.rife.site.ConstrainedProperty; import com.uwyn.rife.site.MetaData; import java.text.DateFormat; public class BlogEntryMetaData extends MetaData { public void activateMetaData() { BlogService service = (BlogService)Rep.getProperties().getValue("blogService"); Blog blog = service.getBlog(); DateFormat format = DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG, blog.getLocale()); format.setTimeZone(blog.getTimeZone()); addConstraint(new ConstrainedProperty("date").format(format)); addConstraint(new ConstrainedProperty("excerpt").displayedRaw(true)); addConstraint(new ConstrainedProperty("body").displayedRaw(true)); } }Site structureRIFE's site-structure is the basis of most of the neat features in the web engine, most of which are not applicable to Simon's simple blog example. The declaration of the site-structure can be done in a variety of ways. At the core we have pure Java builders with fluent interfaces (chainable setters) which can be accessed directly, but we additionally have a collection of wrapper declaration languages like Groovy, Janino and XML. The declaration can also be fully automated through the registration of custom handlers, which is for example used by RIFE/Crud to automatically generate entire CRUD interfaces from your domain model. You can freely choose which declaration method you prefer and mix them together. Everybody is thus able to use what feels the most appropriate and the most comfortable. In this example, I opted for the XML declaration method, since it's the most widely used by our users. The reason for this is that it allows you to easily intervene in existing application flows without having to recompile or require access to the sources. You can also easily combine several projects and link their logic flow and data flow together, or change the site-structure on a production server without having to redeploy the entire application. However, please remember, if you have any aversion to XML, you're free to use the plain Java method instead, if that's what you prefer. The site structure of this example is as follows: blog.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE site SYSTEM "/dtd/site.dtd"> <site> <arrival destid="Home"/> <globalexit name="not found" destid="NotFound"/> <!-- Home element --> <element id="Home" implementation="blog.Home" url="home"> <property name="template"><template>blog.home</template></property> <flowlink srcexit="more" destid="Detail"> <datalink srcoutput="entryId" destinput="id"/> </flowlink> </element> <!-- Detail element --> <element id="Detail" implementation="blog.Detail" url="detail/*"> <property name="template"><template>blog.detail</template></property> <pathinfo mapping="$id"/> </element> <!-- NotFound element --> <element id="NotFound" implementation="blog.NotFound"> <property name="template"><template>blog.notfound</template></property> </element> </site>You can clearly see that each element has an ID and an implementation. They also declare a " The The The Servlet containers and the repositoryRIFE takes care of the entire life-cycle of your application through what we call the Repository. By adding the RIFE filter to This is the relevant part of the <filter> <filter-name>RIFE</filter-name> <filter-class>com.uwyn.rife.servlet.RifeFilter</filter-class> <init-param> <param-name>rep.path</param-name> <param-value>participants.xml</param-value> </init-param> </filter> <filter-mapping> <filter-name>RIFE</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>The repository contains a number of participants and application-wide properties. Participants will be started up and shut down together with your application, and they serve as object factories or services. RIFE will handle inter-participant dependencies transparently and figures out the appropriate execution order. This is the repository declaration of our example: participants.xml <rep> <property name="blogService"><participant name="BlogServiceProvider"/></property> <participant name="BlogServiceProvider">blog.participants.BlogServiceProvider</participant> <participant param="blog.xml">ParticipantSite</participant> </rep>
blog/participants/BlogServiceProvider.java package blog.participants; import com.uwyn.rife.rep.SingleObjectParticipant; import domain.BlogService; public class BlogServiceProvider extends SingleObjectParticipant { public Object getObject() { return new BlogService(); } }The second participant initializes the site structure by using the ConclusionRIFE clearly isolates all important parts and provides a component-based web application development model with very intuitive code inside your view layer. The site structure provides a high-level overview of your application and takes care of all state management and URL handling. You get access to powerful features like native Java continuations, state handling without sessions, URL localization, conversational state, three-dimensional flows, content management integration, transparent meta data merging and much more. The simple things however still remain very easy to do. You can download a ready-to-run archive of this example to play with, or browse the Subversion repository. |
||||||
Comments |
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
|
||||||
Add a new comment |
||||||
|
||||||
| < JHighlight 1.0 released | TSSJS is gonna rock! > |






