ITC382 11255412 A1B

From PeacockWiki

Jump to: navigation, search
Assignment 1: Journal Non-Technical Article Technical Article Technical Report Demo App


Contents

Technical Report

Legend:

These boxes will detail problems and personal experiences in developing this app
These boxes will show references used in developing parts of the app, and are a pointer to useful information.

Brief installation overview

  1. Acquire Java JDK, Apache Ant, Eclipse 3.1.x, Tomcat 4.1.30 (Newer versions should work fine), Tapestry 3.0.3, and optionally the Spindle 3.2.0 update site zip (or download directly using eclipse).
    Links
  2. Install Java, Apache Ant, and Tomcat. Use Ant to deploy Tapestry to the tomcat install.
    • Execute the Java download file, and follow wizard.
    • Extract the ant archive to a permanent location, and add the bin directory to the your path.
    • Extract the tomcat archive. Either add JAVA_HOME to your environment variables, or set the variable in the startup and shutdown scripts in the bin directory. JAVA_HOME points to the installation point of the JDK, the directory above the bin directory containing java.exe and javaw.exe.
    • Extract the tapestry archive to a temporary location, and follow the instructions in Readme.html under "Configuring Tomcat"
      I found that the install failed on downloading Javassist, if you have this problem, try editing the javassist.loc property in config/common.properties, replacing the server telia.dl.sourceforge.net with your own choice of sourceforge mirror, or simply replace the entire link with a selection from http://prdownloads.sourceforge.net/jboss/javassist-2.5.1.zip?download
  3. Copy tapestry libs into tomcat lib folder
    • Copy all files from webapps/workbench/WEB-INF/lib to shared/lib. Alternatively, ensure these files are copied into webapps/project-name/WEB-INF/lib for every tapestry webapp.
  4. Install Eclipse and Spindle
    • Extract the eclipse archive to a permanent location, and run the executable.
    • Select Help → Software Updates → Find and Install, choose Select new features to install.
    • Proceed with one of the following
      • Choose New Remote Site, and enter the name Spindle, and the UpdateSite URL (http://spindle.sourceforge.net/updates)
      • Extract the UdateSite Zip, choose New Local Site, and select the location of the extracted files.
    • Click Finish
    • Explore the tree and select "Spindle 3, and Eclipse Plugin for Tapestry 3.2.0"
    • Proceed through the wizard to install spindle
    • Restart the workbench when prompted

Sample project

This section will detail the construction of my sample project, how it works, and problems and solutions encountered in its development.

Creating the project

File → New → Project (or first icon on toolbar)

Name the project ("lists")

Continue Next, and Finish (leave all options default).

Set project compiler options, right click on the project, and choose Properties. Select Java Compiler, and select Enable Project Specific Settings. Choose a Compiler Compliance Level of 5.0. (alternatively this can be done through Window → Preferences → Java → Compiler, to set this property for all projects)

Open web.xml, and add the following lines

    <filter>
        <filter-name>redirect</filter-name>
        <filter-class>org.apache.tapestry.RedirectFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>redirect</filter-name>
        <url-pattern>/</url-pattern>
    </filter-mapping>

The default path of a Tapestry app is /app-name/app . If you visit /app-name/ you will receive an error. These lines will instruct the servlet container (tomcat) to redirect the client to the application.

Open Home.html and place some text between the body tags. Save the file.

Download tomcat-local-deploy.xml and import it in the root directory of the project (File → Import → File System). Open the file in eclipse, and check the properties match your local configuration, then choose Run → External Tools → Run As → Ant Build. This should install the test app into your tomcat installation, which should be visible by point a browser to http://localhost:8080/lists/ .

Download lists-model.zip and import it to the root directory of the project also (File → Import → Archive). It contains the java classes of the data model used in this app.

The Scenario

This app is designed to allow a user to browse mailing list archives. My mail server runs Apache James, and it is set to save mailing list emails in a designated directory. For the purposes of this test I am using a sample archive. The archive may be extracted to C: root (C:\), it will create a TEMP directory with the following structure

C:\
  TEMP
    lists
      lists
        4D61696C313131303238393436363630312D30.Repository.FileStreamStore
        4D61696C313131303436313636363131392D3932.Repository.FileStreamStore
      test
        4D61696C313131303238393436363630312D30.Repository.FileStreamStore
        4D61696C313131303436313636363131392D3932.Repository.FileStreamStore
        public

This application has no security built into it, apart from the fact you cant access a private group unless you know its name. Public archives are marked by an empty file named public in that archives directory, and are listed by the app for all to see.

Download the archive, and extract it to C:\ . If you do choose another application, be sure you change the path in the java code later on (for this demo the path is hard-coded in the app).

The app will consist of 3 pages, the first allowing the user to select a public list, or enter a private list to view. The second will show all posts to that list, allowing the user to select one to view. The third page will be a view of that email.

Page Layout

First thing to do is to create a page template that can be used by all pages in the app. To do this create a new component in Eclipse (File → New → other → Tapestry → Tapestry Component). Name the component "Page", and leave all options as default. Page.jwc is fine as default. Page.html should contain the following

<html jwcid="@Shell" title="Mail Lists">
  <body jwcid="@Body">
    <span jwcid="@PageLink" page="Home">Home</span><br />
    <span jwcid="@RenderBody">Page content goes here.</span>
  </body>
</html>

Tapestry components are called by the inclusion of a jwcid attribute in a html tag. The original tag is ignored by tapestry, but is included so normal WYSIWYG editors can successfully edit the file, and the tag is normally made to be the same as the tapestry component used will generate. Span tags are typically used when the component will not return a html tag (for eg. if it just returns a line of text). Additional parameters may be sent to the tapestry component simply by adding them as normal parameters in the HTML tag.

Shell and Body construct the HTML and BODY tags, and are not absolutely necessary, as these tags could be entered manually. Shell and body simply save a bit of time.

PageLink creates a link to the specified page, in this instance "Home". So every page using this template will have a link to the main page of the app.

Now open Home.html, and replace any content with the following.

<span jwcid="@Page">
   Select a List to view:<br/>
</span>

As you can see, this template calls the Page component defined above.

The RenderBody tag in the Page.html takes the content of the calling template (in this case "Select a List to view:<br/>"), and passes it through the tapestry engine to be processed and outputted to the browser.

The ant build script should be able to be run now, and by refreshing the browser you should see a page with a link and a line of text.

Selector Component

Create a new component called "ListSelector", and this time in the New Tapestry Component wizard, choose Create a new Class. The Class name will be entered automatically. Enter a package name similar to "com.yourname.tapestry.list".

Excluding the package specifier, the file should look similar to:

import java.util.ArrayList;
import org.apache.tapestry.BaseComponent;
import org.apache.tapestry.IRequestCycle;
import com.peacocktech.tapestry.lists.model.*;

public class ListSelector extends BaseComponent {

    //name entered from form
    private String listName;

    public List list;

    private Lists lists;

    public ListSelector() {
        super();
        lists = new Lists();
    }
    
    //Form Action
    public void selectList(IRequestCycle r) {
    }

    public String getListName() {
        return listName;
    }

    public void setListName(String listName) {
        this.listName = listName;
    }

    //retrieve a list of public lists for display
    public ArrayList getPublicList() {
        return lists.getPublicLists();
    }

    //link action for public lists
    public void viewList(IRequestCycle r) {
    }
}

Update ListSelector.html as:

<form jwcid="@Form" listener="ognl:listeners.selectList">
<input jwcid="@TextField" value="ognl:listName" size="12"/>
<input type="submit" value="Login"/>
</form>
<ul>
  <li jwcid="@Foreach" source="ognl:publicList" value="ognl:list" element="li">
    <span jwcid="@ActionLink" listener="ognl:listeners.viewList">
      <span jwcid="@InsertText" value="ognl:list.name"/>
    </span>
  </li>
</ul>

Finally update Home.html with an extra line

<span jwcid="@Page">
  Select a List to view:<br/>
  <span jwcid="@ListSelector"/>
</span>

The build script may be run again, and the app will show a form and a "list" of lists (currently containing only one item).

The @ListSelector tag instructs the template to load ListSelector.jwc and associated files, and execute them as a template embedded in Home.html. Note that ListSelector.html has no html or body tags, as a component is generally embedded in a page that already has these tags.

ListSelector.html contains a number of new tags.

@Form generates a html form. The listener property instructs tapestry to call selectList in ListSelector.java when the form is submitted. @TextField adds a html form text field, whose value is saved to listName via getter and setter methods (detected automatically by tapestry using reflection). Tapestry automatically assigns a name to the field, and handles transferring data to the class and calling the listener method. A standard submit button is added to the form.

@ForEach loops items in an Iterator, Collection, Object[], or Object. In this case, the source is publicList (the result of a call to getPublicList(), which returns an ArrayList). In each iteration the current value being iterated is stored in list (public field in ListSelector.java), to be accessed by other components. @InsertText reads this value, and echoes the result of getName of the object stored in list (an instance of an object in the data model).

View List

Create a new Tapestry Page called ViewList, again, creating a class for it in the same package as previously. Enter the following into ViewList.java

import java.util.ArrayList;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.html.BasePage;
import com.peacocktech.tapestry.lists.model.*;

public class ViewList extends BasePage {
    private List list;
    public Post post;

    public String getListName() {
        return list.getName();
    }

    public void setList(List list) {
        this.list= list;
    }
    
    public ArrayList<Post> getPosts()
    {
        return list.getPosts();
    }
    
    public void viewPost(IRequestCycle r)
    {
    }

}

And the following into ViewList.html

<span jwcid="@Page">
Viewing List: <span jwcid="@InsertText" value="ognl:listName"/><br />
<table border="1">
  <tr jwcid="@Foreach" source="ognl:posts" value="ognl:post" element="tr">
    <td><span jwcid="@Insert" value="ognl:post.date"/></td>
    <td><span jwcid="@Insert" value="ognl:post.from" raw="true"/></td>
    <td><span jwcid="@ActionLink" listener="ognl:listeners.viewPost">
      <span jwcid="@Insert" value="ognl:post.subject"/>
    </span></td>
  </tr>
</table>
</span>

Relationships in this are similar to the last section. @Page calls our first component to set up the html and body tags. listName calls getListName in the java file, posts calls getPosts, while post refers to the post field. post again is an object from the model, containing methods getDate, getFrom and getSubject. listeners.viewPost refers to viewPost from the java code.

To get this page working, we must flesh out the two methods from the previous component, ListSelector.java

    public void selectList(IRequestCycle r) {
        ViewList viewList = (ViewList) r.getPage("ViewList");
        viewList.setList(lists.getList(listName));
        r.activate(viewList);
    }
    public void viewList(IRequestCycle r) {
        ViewList viewList = (ViewList) r.getPage("ViewList");
        viewList.setList(list);
        r.activate(viewList);
    }

Both methods perform a similar task. They retrieve an instance of ViewList, instruct it which list to present, and call upon it to display. The selectList is called form the form, it searches lists for the correct list, while viewList is used in the for loop. Tapestry automatically sets the link field, which is used to instruct ViewList which list to present. Ideally both method should implement some error checking to ensure the chosen lists actually exist.

Again, run the build script, and you should be able to view a list of posts, either by clicking on test, or by entering "lists" in the form.

View Post

Now for this page, instead of simply showing the email, we will create a re-useable component to do it for us, so make a new page called "ViewPost", and enter the following into your java file.

import org.apache.tapestry.html.BasePage;
import com.peacocktech.tapestry.lists.model.*;

public class ViewPost extends BasePage {
    private Post post;

    public Post getPost() {
        return post;
    }

    public void setPost(Post post) {
        this.post = post;
    }
}

Now create a simple page to show a heading, and call upon our component that will be created in the next section.

<span jwcid="@Page">
<H1>View Email</H1>
<span jwcid="@ViewEmail" email="ognl:post"/>
</span>

Flesh out viewPost in ViewList.java in the previous section.

    public void viewPost(IRequestCycle r)
    {
        ViewPost viewPost = (ViewPost)r.getPage("ViewPost");
        viewPost.setPost(post);
        r.activate(viewPost);
    }

View Email

Create a new component called ViewEmail.

This component is slightly more complex than previous, because it has a manually specified parameter. To add the parameter, add the following line to ViewEmail.jwc

    <parameter name="email" required="yes" type="com.peacocktech.tapestry.lists.model.Post"/>

This line instructs Tapestry that the component is to have a required parameter called "email", and that it should contain a Post object. Note in the previous section ViewPost.html contained a jwcid="@ViewEmail" calling this component, and it also had email="ognl:post". That tells tapestry to send the post property to this component.

Insert the following code into the java file.

import org.apache.tapestry.BaseComponent;
import org.apache.tapestry.IBinding;
import com.peacocktech.tapestry.lists.model.*;

public class ViewEmail extends BaseComponent {
    public Post getEmail() {
        IBinding binding = (IBinding) getBinding("email");
        if (binding.getObject() != null) {
            return (Post) binding.getObject();
        }
        return null;
    }
}

The getEmail method retrieves the email parameter from tapestry. From here on things are pretty standard

ViewEmail.html is as follows.

<b>Post From </b><span jwcid="@Insert" value="ognl:email.from" raw="true"/>
<b>On </b><span jwcid="@Insert" value="ognl:email.date"/><br/>
<H2><span jwcid="@Insert" value="ognl:email.subject"/></H2>
<table><tr>
  <td width="2" bgcolor="black"></td>
  <td><font size="-1">
    <span jwcid="@InsertText" value="ognl:email.header"/>
  </font></td>
</tr></table><p/><p/>
<span jwcid="@InsertText" value="ognl:email.message"/>

Email in this template refers to the getEmail method, which in turn reads the parameter passed to the component. This component can be used anywhere in this application as long as it is given a valid Post object to read.

Conclusion

Tapestry is indeed a powerful and easy to use framework. Its major downfall is its steep learning curve, which isn't helped by the fact good resources for beginners are hard to find. As an example based learner, I found it best to find some working samples, and pull bits out to create my own working demo's. Its not until you start to get over the learning curve that references like the Component reference and Java API become of any use.

It takes a while to realise what Tapestry is doing in the background, that OGNL expressions relate to getters and setters in Java code, and to understand that Tapestry maintains object state between sessions automatically. It is intuitive, but someone who has worked with pure servlets and JSP may find it peculiar to use because Tapestry does so much work for you that you never expected to be done for you.

Version 4 of Tapestry promises much better user guides, and with its own improvements I hope will prove to be as popular as it is powerful.



Assignment 1: Journal Non-Technical Article Technical Article Technical Report Demo App
Personal tools