RSF Content Example

Fundamental to any web application is the ability to include content in a web page. RSF provides the UIOutput() compoment to insert text into the compoment before it is rendered into HTML. This example shows how content from different sources can be added to the output.

XHTML Template

The following view template is defined by main.html:

<!DOCTYPE html      PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:rsf="http://ponder.org.uk/rsf">
  <head>
    <title>Sakai RSF - Content Example</title>
  </head>
  <body>
    <h1>Sakai RSF - Content Example</h1>
    <div rsf:id="msg1">This is message 1.</div>
    <div rsf:id="msg2">This is message 2.</div>
    <div rsf:id="msg3">This is message 3.</div>
    <div rsf:id="msg4">This is message 4.</div>
  </body>
</html>

This file is required to comform to the XHTML specification . In particular, that means that all tags must be closed properly and all parameters must be enclosed by quotes.

The template shown here provides a basic web page structure. This could be considerably more complicated, but is simplified here for clarity. Elements to be hanlded by RSF are marked with an rsf:id parameter. All other tags, such as "<h1>Sakai RSF - Content Example</h1>" will fall through the rendering process and appear in the output. Four DIV tags are used as place holders for text to be entered by the application. If, for some reason, the generation fails, text will default to the strings provided here, such as "This is message 1.".

Spring Context

Two context files are typically included with any RSF based application: an application scope and a request scope. These define beans that may be accessed by the application code using Spring injection or the RSF Expression Language (RSF-EL).

First, the applicationContext.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
  "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  <bean id="hellobean" class="org.sakaiproject.tool.simple.beans.HelloBean"/>

  <!-- Define the bean roots from the request scope file that are accessible 
  via incoming request URL (not strictly necessary for this app) -->
  <bean parent="requestAddressibleParent">
    <property name="value" value="hellobean"/>
  </bean>
    
  <!--  Spring messageSource replaces JSF message-bundle from faces-config.xml -->
  <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="basename" value="classpath:org/sakaiproject/tool/ex/bundle/Messages" />
    <property name="cacheSeconds" value="10" />
  </bean>
  
  <!-- Define the component producer for the main view -->
  <bean id="MainProducer" class="org.sakaiproject.tool.simple.components.MainProducer">
  	<property name="helloBean" ref="hellobean" />
    	<property name="messageLocator" ref="messageLocator" />
  </bean>
</beans>

Second, the requestContext.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
	"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<!--  none for this example.  -->
</beans>

In this example, all beans have application scope, meaning the stick around for as long as the application is resident in Tomcat. By comparison, request scope beans are created at the start of a request and destroyed when the request is complete. Beans may refer to beans of longer scope than they, but not the other way around.

Three beans are important to this example. The HelloBean will be accessed for content and is injected into MainProducer. The messageSource bean is a special bean managed by RSF. It is used to create another bean called messageLocator, which is also injected into MainProducer. Finally, MainProducer is our view producer bean.

Tool Registration

All Sakai tools must be registered at Tomcat starutp time via the ToolListener. This class looks for a registration XML file in the application. The file is named for the tool being registered, "sakai.rsf.ex1.xml".

<?xml version="1.0"?>
<registration>
	<tool id="sakai.rsf.ex1" 
		title="Sakai RSF Example 1"
		description="Content examles.">

		<configuration name="rsfsimple.property.msg" value="[Message here]" />
		<category name="sakai.sample" />
		<keyword name="rsf-examples" />
	</tool>
</registration>

For this example, only the tool id is important. The other entries are include as examples of what can be done here. Title and description will be displayed in Mercury, if you are using it for development. Configuration parameters are used by the Sakai Site Tool when adding a tool to the site. Category and keyword is also used by Mercury to sort available tools.

Tomcat Application Configuration

Each Sakai tool is also a tomcat application. It may be defined as a servlet, in which case it is reponsible for responding to requests and generating appropriate output. Alternatively, various MVC (model-view-controller) approachs can be used such as JSP, Struts, Spring MVC, JSF, or RSF. This example shows an RSF application described in web.xml:

<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
	  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
  version="2.4">

<!-- CONFIG This is the name of the "punch-through" URL which will access the
    servlet directly bypassing the Sakai request filter. Paths beginning with
    / will be considered RELATIVE TO THE FIRST SLASH APPEARING IN THE REQUEST
    URL. Other paths will be considered as absolute URLS. 
    THIS MUST AGREE WITH THE DEPLOYED WEBAPP NAME, i.e. the artifactId of this
    tool, where the Sakai plugin build was used.
    -->
  <context-param>
    <param-name>resourceurlbase</param-name>
    <param-value>/rsf-ex-1/</param-value>
  </context-param>

  <!-- Configure standard Spring application contexts. Be sure to mention
    rsf config files first, so any overrides may be processed. The first two
    config files are loaded from inside the rsfutil.jar  -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      classpath:conf/rsf-config.xml,
      classpath:conf/blank-applicationContext.xml,
      classpath:conf/sakai-applicationContext.xml,
      /WEB-INF/applicationContext.xml
    </param-value>
  </context-param>

  <!-- Configure "request scope" Spring application contexts (RSAC).
    Be sure to mention rsf config files first, so any overrides may be
    processed. -->
  <context-param>
    <param-name>requestContextConfigLocation</param-name>
    <param-value>
      classpath:conf/rsf-requestscope-config.xml,
      classpath:conf/blank-requestContext.xml,
      classpath:conf/sakai-requestContext.xml,
      /WEB-INF/requestContext.xml
    </param-value>
  </context-param>


  <!--  The Sakai Request Handler - standard request filtering.-->
  <filter>
    <filter-name>sakai.request</filter-name>
    <filter-class>org.sakaiproject.util.RequestFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>sakai.request</filter-name>
    <servlet-name>sakai.rsf.ex1</servlet-name>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
  </filter-mapping>

  <!--  The RSF Servlet.  -->
  <servlet>
    <!-- This must agree with the name advertised in the tool registration. -->
    <servlet-name>sakai.rsf.ex1</servlet-name>
    <servlet-class>
      uk.ac.cam.caret.sakai.rsf.servlet.ReasonableSakaiServlet
    </servlet-class>
    <!--<load-on-startup>1</load-on-startup>-->
  </servlet>
  
  <!-- URL mapping has handled by SakaiRequestParser -->
  <servlet-mapping>
    <servlet-name>sakai.rsf.ex1</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>

  <!--  The ToolListener causes this tool to be registerd with Sakai.  -->
  <listener>
    <listener-class>org.sakaiproject.util.ToolListener</listener-class>
  </listener>

  <listener>
    <listener-class>
      org.sakaiproject.util.ContextLoaderListener
    </listener-class>
  </listener>

</web-app>

There's a lot that goes on in here. The key things to understand for an RSF application is that the "resourceurlbase" parameter value must match the maven artifactId. All references to servlet-name should agree with the id defined in the tool registration file, "sakai.rsf.ex1" in this case. The managing servlet is ReasonableSakaiServlet, provided in the RSF support package. Two context files are used: applicationContext and requestContext. This provide two different levels of scope: application and request scope. Session scope can also be defined, if needed.

Message Bundle

A message bundle allows strings to be defined exernal to the application, and translated based on a locale code. The default strings are defined in Messages.properties:

ex_msg3=Text from a message bundle that can be translated.
ex_msg4=Text from a message bundle via the RSF-EL.

HelloBean Code

A simple data bean is provided as a way to demonstrate access to a bean for content:

package org.sakaiproject.tool.simple.beans;
import java.util.List;
import java.util.Vector;

public class HelloBean {
  private String message = "Text provided from an injected bean.";

  public void setMessage(String message) {
    this.message = message;
  }

  public String getMessage() {
    return message;
  }
}

This is a trivial example that just returns a string. A more powerful example would access a file or database for information.

View Producer Code

A class must be defined that corresponds to each template view. In this case, MainProducer is paired with main.html, as defined by the VIEW_ID:

package org.sakaiproject.tool.simple.components;

import uk.org.ponder.rsf.components.UIContainer;
import uk.org.ponder.rsf.components.UIOutput;
import uk.org.ponder.rsf.viewstate.ViewParameters;
import uk.org.ponder.rsf.view.ComponentChecker;
import uk.org.ponder.rsf.view.DefaultView;
import uk.org.ponder.rsf.view.ViewComponentProducer;
import org.sakaiproject.tool.simple.beans.HelloBean;
import uk.org.ponder.messageutil.MessageLocator;


public class MainProducer implements ViewComponentProducer, DefaultView {
  public static final String VIEW_ID = "main";

  public String getViewID() {
    return VIEW_ID;
  }
  public HelloBean helloBean = null;

  public void setHelloBean (HelloBean hb) {
	  this.helloBean = hb;
  }

  private MessageLocator messageLocator;

  public void setMessageLocator(MessageLocator messageLocator) {
    this.messageLocator = messageLocator;
  }

  public void fillComponents(UIContainer tofill, ViewParameters params, ComponentChecker checker) {
    UIOutput.make(tofill, "msg1", "Text computed in the producer.");
    UIOutput.make(tofill, "msg2", null, "#{hellobean.message}");
    UIOutput.make(tofill, "msg3", messageLocator.getMessage("ex_msg3"));
    UIOutput.make(tofill, "msg4", null, "#{messageLocator.ex_msg4}");
  }
}

This is where most of the action takes place. The fillComponents() method is called to create a compoment tree that will be used to render the output. A UIContainer is passed as the root of this tree. Components are added that correspond to the rsf:id's included in the main.html template. Four compoments are added, one for each DIV tag in the template. Each of these usese the UIOutput class to create an output component.

The "msg1" instance shows text being computed on the fly.
The "msg2" instance uses the RSF-EL to access the message included in HelloBean. This could also be accessed via MainProducer.helloBean, since it injected.
The "msg3" instance uses the injected messageLocator to get the message that corresponds to the code "ex_msg3".
Finally, the "msg4" instance uses RSF-EL to access the messageLocator for a different message, "ex_msg4".

Note the UIOutput.make() has two forms. One to insert text directly, one to use the expression langauge to get data. The null included in these calls is a default string that could be used if EL expression fails.

Maven Project

Finally, maven project is needed to define all dependencies and build the example application.

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <pomVersion>3</pomVersion>
  <extend>../../master/project.xml</extend>
  <artifactId>rsf-ex-1</artifactId>
  <name>RsfEx1</name>
  <groupId>sakaiproject</groupId>
  <currentVersion>${sakai.version}</currentVersion>

	<properties>
		<!-- deploy as a war -->
		<deploy.type>war</deploy.type>
	</properties>

  <organization>
    <name>Nolaria Consulting</name>
    <url>http://wwww.nolaria.com</url>
  </organization>
  <inceptionYear>2007</inceptionYear>
  <description>RSF Example 1 - Content</description>
  <repository />

  <dependencies>

    <!-- begin standard RSF dependencies here -->
    <dependency>
      <groupId>ponderutilcore</groupId>
      <artifactId>ponderutilcore</artifactId>
      <version>${ponderutilcore.version}</version>
      <type>jar</type>
      <properties>
        <war.bundle>true</war.bundle>
      </properties>
    </dependency>
    <dependency>
      <groupId>j-servletutil</groupId>
      <artifactId>j-servletutil</artifactId>
      <version>${jservletutil.version}</version>
      <type>jar</type>
      <properties>
        <war.bundle>true</war.bundle>
      </properties>
    </dependency>
    <dependency>
      <groupId>rsfutil</groupId>
      <artifactId>rsfutil</artifactId>
      <version>${rsfutil.version}</version>
      <type>jar</type>
      <properties>
        <war.bundle>true</war.bundle>
      </properties>
    </dependency>

    <dependency>
      <groupId>servletapi</groupId>
      <artifactId>servletapi</artifactId>
      <version>2.4-20040521</version>
      <type>jar</type>
    </dependency>
     <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.9</version>
      <type>jar</type>
      <properties>
        <war.bundle>true</war.bundle>
      </properties>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring</artifactId>
      <version>1.2.8</version>
      <type>jar</type> 
    </dependency>

    <!--  The framework requires CGLIB (Java bytecode manipulation library) 
    in order to implement lazy-loaded beans  -->
    <dependency>
      <groupId>aopalliance</groupId>
      <artifactId>aopalliance</artifactId>
      <version>1.0</version>
      <type>jar</type> 
    </dependency>
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>nodep-2.1_3</version>
      <type>jar</type> 
    </dependency>  

    <dependency>
      <!-- Doug Lea's Oswego concurrency package. The forerunner of JSR 166
        java.util.concurrent -->
      <groupId>concurrent</groupId>
      <artifactId>concurrent</artifactId>
      <version>1.3.4</version>
      <type>jar</type>
      <properties>
        <war.bundle>true</war.bundle>
      </properties>
    </dependency>
    <!-- Aleksander Slominski's (U Indiana) lightning-fast XML pull parser -->
      <dependency>
      <groupId>xpp3</groupId>
      <artifactId>xpp3</artifactId>
      <version>1.1.3.4-RC8_min</version>
      <type>jar</type>
      <properties>
        <war.bundle>true</war.bundle>
      </properties>
    </dependency>

    <!-- Sakai Util is required to resolve user's Locale -->
    <dependency>
      <groupId>sakaiproject</groupId>
      <artifactId>sakai-util</artifactId>
      <version>${sakai.version}</version>
      <type>jar</type>
      <properties>
        <war.bundle>true</war.bundle>
      </properties>
    </dependency>
    <dependency>
      <groupId>sakairsf</groupId>
      <artifactId>sakairsf</artifactId>
      <version>${rsfutil.version}-sakai_${sakai.version}</version>
      <type>jar</type>
      <properties>
        <war.bundle>true</war.bundle>
      </properties>
    </dependency>
  </dependencies>

  <build>
    <sourceDirectory>src/java</sourceDirectory>
	<resources>
		<resource>
			<directory>${basedir}/src/bundle</directory>
			<includes>
				<include>**/*.properties</include>
			</includes>
		</resource>
	</resources>
  </build>
</project>

As note before, the artifactId is also used as the resourceurlbase parameter in web.xml. This is also the name of the WAR created. Also note how resources are included in the BUILD section. Most of the dependencies in this file are necessary to include RSF and it's support code. A few Sakai utilities are include for ToolListener, etc.