Pustefix Tutorial

Tobias Fehrenbach

Stephan Schmidt


Table of Contents

Introduction
1. Getting started
1.1. Requirements
1.2. Prepare the project directory
1.3. Unpack the skeleton
1.4. Configure Eclipse
1.5. Build parameters & first build
2. Basic tutorial
2.1. Setting up a new project
2.2. Generated files
2.3. Creating the pages
2.4. Setting the entry page
2.5. Create the input form
2.6. Implementing the business logic
2.6.1. Implementing a wrapper
2.6.2. Interlude: Implementing a bean
2.6.3. Implementing a handler
2.6.4. Implementing a ContextResource
2.6.5. Accessing a ContextResource
2.7. Implementing the workflow
2.8. Displaying errors
2.9. Displaying the entered data
2.10. Saving the data
2.11. The finishing touch
2.11.1. Changing data
2.11.2. Avoiding unwanted access
2.12. Conclusion
3. Usermanager tutorial
3.1. Setup
3.2. Create a new bean
3.3. Create the handler
3.4. Create a caster
3.4.1. Create a new statuscode for the caster
3.4.2. Create the ToURL-caster
3.5. Annotate the User bean
3.5.1. Annotate the class declaration
3.5.2. Annotate the getter
3.6. Generate wrapper and StatusCodeLib
3.7. Create a userlist class
3.8. Adjust the servlet configuration
3.9. Add a new user
3.10. Create the pages
3.10.1. Create a page for user data input
3.10.2. Create page for listing users
3.10.3. Add pages to xml configuration files
3.11. Add a pageflow
3.12. Try to add your own user
3.13. Add sample users
3.13.1. Add new constructors to your UserList
3.13.2. Add init method to UserList
3.14. New feature: Delete an user
3.14.1. Create a new bean
3.14.2. Create a new handler
3.14.3. Annotate the DeleteUser bean
3.14.4. Generate the wrapper class
3.14.5. Add a delete user method
3.14.6. Delete a user
3.14.7. Add a delete button
3.14.8. Adjust the servlet configuration
3.15. New feature: Edit an user
3.15.1. Create a new bean
3.15.2. Create a new handler
3.15.3. Annotate the EditUser bean
3.15.4. Generate the wrapper class
3.15.5. Add a method to replace a user of our UserList class
3.15.6. Edit an user
3.15.7. Add an edit button
3.15.8. Adjust the servlet configuration
3.15.9. Add user to form
3.15.10. Replace method handleSubmittedData of UserHandler
3.16. Conclusion
4. AJAX Calculator tutorial
4.1. Setup
4.2. Using the webservice servlet
4.3. Implementing the business logic
4.4. Exposing the service
4.5. Consuming the service
4.6. Conclusion

List of Figures

2.1. The new Pustefix project

List of Tables

4.1. Global webservice configuration
4.2. Local webservice configuration

Introduction

These tutorials are meant as a starting point for developers that have never worked with Pustefix. They introduce you to the different types of applications that can be implemented with Pustefix. Of course, they can only scratch at the surface of most of the features, if you are interested in an in-depth look at Pustefix, refer to the reference documentation.

The source code used in the tutorials is available for public checkout. To test the tutorials on you local development machine, execute the following commands:

$ svn co https://pustefix.svn.sourceforge.net/svnroot/pustefix/trunk/pfixtutorial
$ cd pfixtutorial
$ ant
$ ./startTomcat.sh

Of course, you can also start with a skeleton application and implement the tutorials from scratch.

Getting started

1.1. Requirements

Before we can get started, you have to make sure that some requirements are met by your development environment. You will need:

  • JDK 5.0 or newer

  • POSIX-like operating system (Pustefix has been tested with Linux and Mac OS X, but might also work with other systems like *BSD)

  • Apache Tomcat 5.5.x

  • Apache Ant 1.6.5 or newer

The installation of these tools (except Tomcat) is not covered by this tutorial. Please refer to the documentation provided with these tools for installation instructions.

1.2. Prepare the project directory

If you are not using Eclipse, you can just create an empty directory that will contain the project files and proceed with Section 1.3, “Unpack the skeleton”.

Start the Eclipse workbench and create a new project of type Java Project". Make sure that you choose separate source and build folders: Use src for the source and build for the build folder. This is important because the Pustefix build script expects these folders.

1.3. Unpack the skeleton

Download the newest pfixcore-skel-X.X.X.tar.gz from Pustefix's downloads page. Unpack the archive to a temporary directory. A new directory with the name skel will be created. Copy the content of this directory to your new project directory.

Now you need to download Apache Tomcat. Choose the .tar.gz archive from the download page and place it in the lib/tomcat directory of your project directory.

After you are done with that, refresh the resources view in Eclipse to make the new files appear.

1.4. Configure Eclipse

To make features like auto-completion and auto-build work, you have to import the libraries into Eclipse. Right-click on your project in Eclipse and choose "Build Path" ⇒ "Configure Build Path...". Now use the "Add JARs..." button to add all libraries from the project's lib directory.

As Pustefix generates some classes, you have to add the folder with the generated sources to Eclipse's source path. To make this work choose the "Source" tab in the same dialog you used to configure the build path and add the gensrc folder to the list of source folders.

Finally, you have to configure the path to the JAR file containing Ant. In Eclipse choose WindowPreferences. In the dialog window choose JavaBuild PathClasspath Variables. Choose New... and create a variable with the name PFX_ANT_LIB that contains the path to the lib/ant.jar within your Ant installation directory.

1.5. Build parameters & first build

Within the project directory, create a file called build.properties containing two properties:

standalone.tomcat=true
makemode=test

The first property tells the build process, that we do want to run Tomcat without Apache Httpd integration. Apache Httpd integration can be useful, because static files can be served faster. However this is an advanced topic and for our purposes Tomcat alone will be okay.

The second parameter set the so-called "make mode". This flag can be set to either "test" or "prod" and will cause the editor console to appear in web pages when in "test" mode. In fact you can even make your own settings depend on the make mode, but we will take care of this later. For the moment "test" mode is just what we want. By the way, whenever you switch the make mode, you should do a complete rebuild using ant realclean && ant to make sure, all resources have been built using the same make mode.

Now run ant to perform a first build of the environment. This will create needed symlinks and initialize the environment.

Basic tutorial

Stephan Schmidt

In this tutorial, you will learn how to work with the basic features provided by the Pustefix framework. You will accept user input, store it in the session and display it back to the user. Furthermore, you will create a very simple workflow containing three pages.

The requirements for your application are:

  1. Provide an HTML form that can be used to register new users. The data for a new user must contain: gender, name, email-address, homepage, date of birth and a flag to mark the user as an administrator.

  2. Validate the user data after the page has been submitted and display error information.

  3. If the entered data is correct, move to a new page, which displays the user data and allows the user to choose whether he wants to go back and modify the data or accept the data.

  4. After the data has been stored, display a confirmation page to the user.

In this tutorial application, you will focus on how the requirements will be implemented in the Pustefix framework. There will be no real business logic like actually storing the user data in any data base. These tasks are left to your favorite ORM framework.

2.1. Setting up a new project

Before you can start developing the application, make sure that your system fulfills all requirements that are mentioned in Section 1.1, “Requirements” and that you have build your Pustefix installation at least once, so that all necessary files have been extracted from the Pustefix distribution. More information on building Pustefix can be found in Section 1.5, “Build parameters & first build”

If your environment is set up correctly, you may create a new Pustefix project. For this tutorial, please name the project first-app. A new Pustefix project can be created using the newProject.sh script, which is part of the Pustefix distribution. This script will guide you to the project creation and copy and create all necessary files for you new project.

Please move to your installation directory and start the script:

$ ./newproject.sh
**************************************************
*                                                *
*         Pustefix ProjectGenerator 1.0          *
*                                                *
**************************************************

Please follow the instructions to create a new project.
You can abort the process by pressing Ctrl + C.

Please type in the projects name e.g. "myproject"

After the newProject.sh script has been started, you will be asked for a project name. Please enter first-app at the prompt and press return. The name of the project will be used for several things:

  • It will be used as the name for the folder that contains all project-relevant files inside the projects directory

  • It will also be used as the default sub-domain name for your project.

After you entered the project name and pressed return, the setup script will continue:

Please type in the projects default language (it's english if you leave the field blank).

As your application will be in English, just leave the default language empty and press return. Again, the setup script will continue after you pressed return.

Please type in a comment for the Project
It will be "projectname + comment" if you leave it blank.

Please enter My first Pustefix application as a comment for the project. This will not be visible anywhere for the users of your application.

Please type in a name for the servlet 1

Every Pustefix application at least needs one servlet to process the request. Please enter app and press return to assign a name to the servlet. The setup script will then continue.

Servlet 1 has been added!

Would you like to create another servlet? [yes] [no]

In your first example application, all requests will be processed by the same servlet. Enter no to inform the setup script, that you do not want to add another servlet. New servlets can easily be added during the development of your application.>

Now you provided all necessary information and the setup script will create a new project based on your input.

Creating project in "/path/to/your/installation/projects/" starts now

Creating project folder: first-app
Folder has been created successfully

Creating subfolder starts now
Creating folder conf...
Folder has been created successfully
Creating folder img...
Folder has been created successfully
Creating folder xsl...
Folder has been created successfully
Creating folder xml...
Folder has been created successfully
Creating folder txt & subfolders
Folder has been created successfully
Creating folder htdocs...
Folder has been created successfully

Xml files editing starts now...

Try to read the dom of the given Document: /path/to/your/installation/projects/core/prjtemplates/config.tmpl
Transforming into dom has been successfull
Changing the attribute depend
Changing attribute has been successfull
Changing the attribute name
Changing attribute has been successfull
Changing the attribute name
Changing attribute has been successfull
Changing the attribute name
Changing attribute has been successfull
Writing the file: app.conf.xml
Writing file has been successfull

Writing the file: app.conf.xml
Writing file has been successfull

Try to read the dom of the given Document: /path/to/your/installation/projects/core/prjtemplates/depend.tmpl
Transforming into dom has been successfull
Changing the attribute project
Changing attribute has been successfull
Changing the attribute lang
Changing attribute has been successfull
Changing the attribute handler
Changing attribute has been successfull
Changing the attribute stylesheet
Changing attribute has been successfull
Changing the attribute stylesheet
Changing attribute has been successfull
Changing the attribute xml
Changing attribute has been successfull
Writing the file: depend.xml
Writing file has been successfull

Try to read the dom of the given Document: /path/to/your/installation/projects/core/prjtemplates/frame.tmpl
Transforming into dom has been successfull
Changing the attribute href
Changing attribute has been successfull
Writing the file: frame.xml
Writing file has been successfull

Try to read the dom of the given Document: /path/to/your/installation/projects/core/prjtemplates/project.tmpl
Transforming into dom has been successfull
Changing the value of the tag depend
Changing Textnode has been successfull

Changing the attribute name
Changing attribute has been successfull
Changing the value of the tag comment
Changing Textnode has been successfull

Changing the value of the tag servername
Changing Textnode has been successfull

Changing the value of the tag serveralias
Changing Textnode has been successfull

Changing the value of the tag defpath
Changing Textnode has been successfull

Changing the value of the tag passthrough
Changing Textnode has been successfull

Changing the value of the tag documentroot
Changing Textnode has been successfull

Writing the file: project.xml.in
Writing file has been successfull

Try to read the dom of the given Document: /path/to/your/installation/projects/core/prjtemplates/skin.tmpl
Transforming into dom has been successfull
Writing the file: skin.xsl
Writing file has been successfull

Try to read the dom of the given Document: /path/to/your/installation/projects/core/prjtemplates/metatags.tmpl
Transforming into dom has been successfull
Writing the file: metatags.xsl
Writing file has been successfull

Try to read the dom of the given Document: /path/to/your/installation/projects/core/prjtemplates/page.tmpl
Transforming into dom has been successfull
Changing the value of the tag theme
Changing Textnode has been successfull

Writing the file: main_home1.xml
Writing file has been successfull

Writing the file: main_home1.xml
Writing file has been successfull


Your project has been successfully created.
To see how it works type in "ant".
Afterwards restart Apache httpd and Tomcat.
Then type in "http://first-app.HOSTNAME.DOMAIN"

To complete the setup process, please re-run ant. Now Pustefix will automatically create the Apache and Tomcat configuration for your new project. Please make sure, that you restart Apache and Tomcat, after ant has finished its work.

After you restarted your web server, please open a web browser and open the URL http://first-app.HOSTNAME.DOMAIN. Figure Figure 2.1, “The new Pustefix project” shows the output of the new Pustefix project.

Figure 2.1. The new Pustefix project

The new Pustefix project

2.2. Generated files

The newProject.sh generated a working application for you. All relevant files have been put into projects/first-app. Please take a look at the most important folders in this new project directory:

  • The conf folder contains all configuration files for your applications. These are:

    • project.xml.in contains the information the project, like the domain name where the project can be accessed, the servlets available in the project and some Apache configuration options.

    • app.conf.xml contains the configuration of the app servlet that you created using the setup script.

    • depend.xml contains the configuration of the pages and XSL stylesheets used in your project.

  • The htdocs is empty and can be used to store any resource that should be available via the web server. Possible resources are JavaScript or CSS files.

  • The same applies to the img folder, which apparently is used to store image files.

  • The txt folder contains the text and HTML content of your application. After the setup script has finished, it contains a pages folder which stores the different pages of the application.

  • The xml folder contains the different frames of your application. A frame is an XML document which will be used for every page that is generated. This way, you can easily share header, footer and navigation between all pages.

  • The xsl folder contains XSL stylesheets that are only used in your application. XSL stylesheets provided by Pustefix are not located inside this folder but in projects/core/xsl.

2.3. Creating the pages

Start implementing your application by adding the three needed pages to the application: EnterData, ReviewData and Confirm.

New pages are added by editing the conf/depend.xml file. For each page, you have to add two XML tags:

  • The <page/> tag defines the servlet, that handles the request to this page. All three pages will be handled by the app servlet, which is available at xml/app.

  • The <standardpage/> tag defines the layout of the page by specifying the XML frame that should be used. Again, all three pages will be rendered using the standard frame that has been generated by the setup script.

<?xml version="1.0" encoding="utf-8"?>
<make lang="en_GB" project="first-app">

  <navigation>
      <page handler="/xml/app" name="home1"/>
      <page handler="/xml/app" name="EnterData"/>
      <page handler="/xml/app" name="ReviewData"/>
      <page handler="/xml/app" name="Confirm"/>
  </navigation>

  <!--
    Metatags have been left out.
  -->
  
  <standardpage name="home1" xml="first-app/xml/frame.xml"/>
  <standardpage name="EnterData" xml="first-app/xml/frame.xml"/>
  <standardpage name="ReviewData" xml="first-app/xml/frame.xml"/>
  <standardpage name="Confirm" xml="first-app/xml/frame.xml"/>
  
</make>

You can now open the EnterData page by browsing to http://first-app.HOSTNAME.DOMAIN/xml/app/EnterData.

As you did not provide any content for this page, Pustefix will display an error icon. When hovering over this icon, you can see, that the content of the page in the file first-app/txt/pages/main_EnterData.xml is missing.

This problem can easily be solved by adding this file:

<?xml version="1.0" encoding="utf-8"?>
<include_parts xmlns:ixsl="http://www.w3.org/1999/XSL/Transform" xmlns:pfx="http://www.schlund.de/pustefix/core">
  <part name="content">
    <theme name="default">
      <h1>Register new user</h1>
    </theme>
  </part>
 </include_parts>

If you reload the page, you will see the Register new user headline. Now repeat this step for all three pages.

2.4. Setting the entry page

If you open the application in your browser without specifying the page directly, Pustefix will redirect you to xml/app/home1, which is the default page generated by the setup script. Desired behaviour would be, that the EnterData page is displayed, when your application is started. This can be changed in the servlet configuration in conf/app.conf.xml:

<?xml version="1.0" encoding="utf-8"?>
<contextxmlserver xmlns="http://pustefix.sourceforge.net/properties200401"
                  xmlns:cus="http://www.schlund.de/pustefix/customize"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"
                  xsi:schemaLocation="http://pustefix.sourceforge.net/properties200401 http://pustefix.sourceforge.net/properties200401.xsd">
  
  <servletinfo depend="first-app/conf/depend.xml" name="pfixcore_project:first-app::servlet:app">
    <editmode allow="true"/>
  </servletinfo>
  
  <context defaultpage="EnterData"/>

  <pagerequest name="home1"/>

</contextxmlserver>

The entry page is specified using the defaultpage attribute of the <context/> tag. After you set this attribute to EnterData open the URL http://first-app.HOSTNAME.DOMAIN in your browser and you will be automatically redirected to the page to register new users.

After you changed the entry page, you can get rid of the generated entry page by executing these steps:

  1. Delete the file main_home1.xml from the first-app/txt/pages folder.

  2. Remove the <pagerequest/>> tag from the app.conf.xml file.

  3. Remove the <page/>> and <standardpage/>> tags from the depend.xml file.

2.5. Create the input form

Next, you have to create the HTML form to accept the data of a new user. To create the form, you should not use the standard HTML tags, but the replacements by Pustefix, which automatically write back the data from the business logic to the HTML page.

The form has to be added to txt/pages/main_EnterData.xml:

<?xml version="1.0" encoding="utf-8"?>
<include_parts xmlns:ixsl="http://www.w3.org/1999/XSL/Transform" xmlns:pfx="http://www.schlund.de/pustefix/core">
  <part name="content">
    <theme name="default">
      <h1>Register new user</h1>
      <pfx:forminput>
        <table>
          <tr>
            <td>Gender:</td>
            <td>
              <pfx:xinp type="select" name="user.sex">
                <pfx:option value="m">male</pfx:option>
                <pfx:option value="f">female</pfx:option>
              </pfx:xinp>
            </td>
          </tr>
          <tr>
            <td>Name:</td>
            <td><pfx:xinp type="text" name="user.name"/></td>
          </tr>
          <tr>
            <td>Email:</td>
            <td><pfx:xinp type="text" name="user.email"/></td>
          </tr>
          <tr>
            <td>Homepage:</td>
            <td><pfx:xinp type="text" name="user.homepage"/></td>
          </tr>
          <tr>
            <td>Birthdate:</td>
            <td><pfx:xinp type="text" name="user.birthdate"/></td>
          </tr>
          <tr>
            <td>Administrator:</td>
            <td><pfx:xinp type="check" name="user.admin" value="true"/></td>
          </tr>
        </table>
        <pfx:xinp type="submit" value="register"/>
      </pfx:forminput>
    </theme>
  </part>
 </include_parts>

See the Pustefix reference documentation for more information about the XML tags that have been used in this page.

2.6. Implementing the business logic

Now that you have finished most of the HTML frontend, you should start implementing the business logic. The business logic in Pustefix applications mostly consists of three parts:

  • A wrapper is used to extract the user input from the HTTP-request, executes some checks and casts the data to the desired Java types. A wrapper is also used to write the values and/or error information back to the response. It connects your HTML frontend with your application logic.

  • A handler processes the HTTP request. It extracts the user input from the wrapper, executes additional information and does whatever is necessary in the specific application. You have all the power provided by Java at your command when implementing a handler.

    A handler does not have direct access to the HTTP request, HTTP session or HTTP response.

  • A ContextResource allows you to store any data in the Pustefix Context and thus in the HTTP-session.

    A ContextResource furthermore allows you to add XML data to the ResultDocument which is the Pustefix way to pass information to the HTML frontend.

2.6.1. Implementing a wrapper

When implementing the business logic you will always start by implementing a wrapper. Wrappers in Pustefix are implemented using XML, which will then be used to generate a Java class for the wrapper.

Before you can implement a new wrapper, you will have to create a new Java package org.pustefixframework.tutorial.firstapp.wrapper which will then contain the new wrapper. After the package has been created, create a new EnterUserDataWrapper.iwrp file for the wrapper and paste the following content into this new file:

<interface xsi:schemaLocation="http://pustefix.sourceforge.net/interfacewrapper200401 http://pustefix.sourceforge.net/interfacewrapper200401.xsd"
  xmlns="http://pustefix.sourceforge.net/interfacewrapper200401"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  
  <!-- This handler will process the data -->
  <ihandler class="org.pustefixframework.tutorial.firstapp.handler.EnterUserDataHandler"/>
  
  <!-- Parameters that have to be extracted from the request -->
  <param name="sex" type="java.lang.String" occurrence="mandatory"/>
  <param name="name" type="java.lang.String" occurrence="mandatory"/>
  <param name="email" type="java.lang.String" occurrence="mandatory"/>
  <param name="homepage" type="java.lang.String" occurrence="optional"/>
  <param name="birthdate" type="java.lang.String" occurrence="optional"/>
  <param name="admin" type="java.lang.Boolean" occurrence="optional">
    <default>
      <value>false</value>
    </default>
    <caster class="de.schlund.pfixcore.generator.casters.ToBoolean"/>
  </param>
</interface>

The wrapper defines various options:

  1. Using the <ihandler/> tag, you define the name of the class that will do the request processing for this handler. This class will be implemented at a later point (see Section 2.6.3, “Implementing a handler” if you are too curious).

  2. The different <param/> tags are used to define the different parameters that should be extracted from the HTTP request. For each parameter you define the name and the type of the data. All parameters except the admin-flag are String parameters, the admin-flag should be casted to a boolean value. This can be achieved by setting the type attribute to java.lang.Boolean and supplying a <caster/> tag that specifies a class to to the conversion for you. The class de.schlund.pfixcore.generator.casters.ToBoolean is provided by the Pustefix framework.

    For each parameter you may also specify a default value and define whether the parameter is mandatory or not. For more information on the differen wrapper features, please refer to the reference documentation.

After you created the iwrp definition, please run ant generate-src to generate the Java class for this wrapper:

$ ant generate-src
Buildfile: build.xml

init-dirs:
    [mkdir] Created dir: /home/schst/workspace/pfixtutorial/build
    [mkdir] Created dir: /home/schst/workspace/pfixtutorial/gensrc

tomcat.dir.opt:

tomcat.dir.missing:

ant-tasks-stage1:

ant-tasks-stage2:

ant-tasks:

skel.pre-compile:

pre-compile:
     [echo] Extend the pre-compile target with your own definitions here...

generate-src:
 [pfx-iwrp] Transformed 1 of 1 file, 0 have been up to date

BUILD SUCCESSFUL
Total time: 3 seconds

After ant has finished the build, you will find a new class org.pustefixframework.tutorial.firstapp.wrapper.EnterUserDataWrapper in the gensrc folder of your installation. This file contains all information needed to extract the parameters from the request.

Assigning the wrapper to the page

Now that you have implemented the HTML page and the wrapper you have to connect the HTML form with the wrapper. This is done using the app.conf.xml servlet configuration. If a page contains business logic that must be executed, you have to add a <pagerequest/> tag to the servlet configuration.

Place an <input/> tag inside the <pagerequest/> tag which will act as a container for all wrappers on this page. Each wrapper is registered using an <interface/> tag which requires two parameters to be set:

  1. class specifies the clasname of the wrapper.

  2. prefix specifies the prefix of all request parameters that this wrapper should pay attention to. If you take a look the the HTML page (Section 2.5, “Create the input form”) you will see, that all input fields are prefixed with user and a dot. This way, you can have two wrappers that share parameter names, but will not conflict, as the parameters reside in different namespaces.

To add your new wrapper to the EnterData page, add these lines to the configuration:

<?xml version="1.0" encoding="utf-8"?>
<contextxmlserver xmlns="http://pustefix.sourceforge.net/properties200401"
                  xmlns:cus="http://www.schlund.de/pustefix/customize"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"
                  xsi:schemaLocation="http://pustefix.sourceforge.net/properties200401 http://pustefix.sourceforge.net/properties200401.xsd">
  <!-- ... -->

  <pagerequest name="EnterData">
    <input>
      <interface prefix="user" class="org.pustefixframework.tutorial.firstapp.wrapper.EnterUserDataWrapper" />
    </input>
  </pagerequest>
  
</contextxmlserver>

If you now try to open the page again, Pustefix will respond with an IllegalStateException and the following message:

unable to find class [org.pustefixframework.tutorial.firstapp.handler.EnterUserDataHandler] :org.pustefixframework.tutorial.firstapp.handler.EnterUserDataHandler

This already tells you, that implementing the handler must be your next step. But before you can go on and implement the handler, there is a small task left. Up to now, you do not have a Java type that is able to store the data for a user. This will be done in your next step.

2.6.2. Interlude: Implementing a bean

[Note]Note

This step has absolutely nothing to do with the Pustefix framework. However, as it is needed to understand the example, it still is part of the tutorial.

As you need to store the data submitted by the user, you will need a bean, that is able to store all the information. The following class is a very simple implementation, in your applications you might already have these beans or use a framework, that is generating them for you.

package org.pustefixframework.tutorial.firstapp;

public class User {
    private String name;
    private String email;
    private String birthday;
    private boolean admin;
    private String homepage;
    private String sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getBirthday() {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }

    public boolean getAdmin() {
        return admin;
    }

    public void setAdmin(boolean admin) {
        this.admin = admin;
    }

    public String getHomepage() {
        return homepage;
    }

    public void setHomepage(String homepage) {
        this.homepage = homepage;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

This bean contains exactly the same properties as the wrapper you defined earlier. You will later use this class to store the user information in the session.

2.6.3. Implementing a handler

A handler is responsible to execute the actual business logic of your application. A handler can be any class but is has some requirements that have to be met:

  1. It has to implement the de.schlund.pfixcore.generator.IHandler interface.

  2. As handlers are used as flyweights, they must not have any static non-final properties.

If you use Eclipse to generate a new EnterUserDataHandler class that implements the IHandler interface, you get the following code:

package org.pustefixframework.tutorial.firstapp.handler;

import de.schlund.pfixcore.generator.IHandler;
import de.schlund.pfixcore.generator.IWrapper;
import de.schlund.pfixcore.workflow.Context;

public class EnterUserDataHandler implements IHandler {

    public void handleSubmittedData(Context context, IWrapper wrapper)
            throws Exception {
    }

    public boolean isActive(Context context) throws Exception {
        return false;
    }

    public boolean needsData(Context context) throws Exception {
        return false;
    }

    public boolean prerequisitesMet(Context context) throws Exception {
        return false;
    }

    public void retrieveCurrentStatus(Context context, IWrapper wrapper)
            throws Exception {
    }
}

If you now open the page again, you still get an error, that the page is still not accessible. This is because of the return values of the generated methods and how Pustefix processes a request.

  • When a page is requested, Pustefix calls the prerequisitesMet method of all handlers that are configured for this page. If any of these methods return false, the page will not be displayed.

  • If all of these methods return true, Pustefix will call the isActive method on all handlers of the page. If none of the methods return true, the page will not be displayed.

When Eclipse generated the method bodies, both methods return false und thus the page cannot be displayed. Modify the return values of prerequisitesMet and isActive to make the page accessible

package org.pustefixframework.tutorial.firstapp.handler;

import de.schlund.pfixcore.generator.IHandler;
import de.schlund.pfixcore.generator.IWrapper;
import de.schlund.pfixcore.workflow.Context;

public class EnterUserDataHandler implements IHandler {

    public boolean isActive(Context context) throws Exception {
        return true;
    }

    public boolean prerequisitesMet(Context context) throws Exception {
        return true;
    }

}

If you now open the page again, all form fields will be displayed. To test your form, fill out at least the mandatory fields:

  • gender

    name

    email

If you submit the data, the wrapper will validate your data and then display the page again. At this point, the form elements will still contain the values that you entered. Pustefix saved their state automatically.

If you click on the XML button in the upper right corner of the page, you will see the XML document that contains the data of the rendered page:

<formresult serial="1214247165246">
      <formvalues>
          <param name="user.email">schst@bar.de</param>
          <param name="user.name">Stephan</param>
          <param name="user.sex">m</param>
      </formvalues>
      <formerrors/>
      <formhiddenvals/>
      <wrapperstatus>
          <wrapper active="true" name="org.pustefixframework.tutorial.firstapp.wrapper.EnterUserDataWrapper" prefix="user"/>
      </wrapperstatus>
  </formresult>

The <formvalues/> node contains a <param/> element for each of the form fields that you submitted. Inside the <wrapperstatus/> node, you can see a list of all wrappers that are registered for this page.

As the handler mostly consists of auto-generated code, it does not execute any business logic. If you want to execute Java code after the page is submitted, you only need to place it in the handleSubmittedData method

package org.pustefixframework.tutorial.firstapp.handler;

import de.schlund.pfixcore.generator.IHandler;
import de.schlund.pfixcore.generator.IWrapper;
import de.schlund.pfixcore.workflow.Context;

public class EnterUserDataHandler implements IHandler {

    public void handleSubmittedData(Context context, IWrapper wrapper)
            throws Exception {
        System.out.println("Place business logic here");
    }

}

You will re-visit this class at a later point in the tutorial and add some real logic instead of just debugging code.

2.6.4. Implementing a ContextResource

To be able to transport the user data from one page to the other, you have to store it in the session.

Pustefix does not allow you direct access to the applications HTTP session. Instead it provides you with an easy way to use objects that live inside the session scope. Those objects are called context resources. A context resource can be any class, it just has to meet the following requirements:

  1. There has to be an interface and an implementation for the context resource.

  2. The interface has to extend the ContextResource interface provided by Pustefix.

A context resource, that is able to store a user inside the HTTP session only requires two methods: one to set the user and one to retrieve it from the context resource.

package org.pustefixframework.tutorial.firstapp.contextresources;

import org.pustefixframework.tutorial.firstapp.User;

import de.schlund.pfixcore.workflow.ContextResource;

public interface ContextUser extends ContextResource {
    public void setUser(User user);
    public User getUser();
}

The implementation for this interface also is very easy:

package org.pustefixframework.tutorial.firstapp.contextresources;

import org.pustefixframework.tutorial.firstapp.User;
import org.w3c.dom.Element;

import de.schlund.pfixcore.workflow.Context;
import de.schlund.pfixxml.ResultDocument;

public class ContextUserImpl implements ContextUser {

    private User user;
    
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public void init(Context context) throws Exception {
        // nothing to do here

    }

    public void insertStatus(ResultDocument document, Element element)
            throws Exception {
        // will be implemented later
    }
}

The ContextResource interface forces you to implement the following methods:

  • init will be called when the context resource is created.

  • insertStatus should insert its current state into the DOM tree.

If a context resource is only used as a container to store information in the session, it is not needed to implement any of these methods.

To make this context resource available in your application, you have to register it in the servlet configuration file app.conf.xml:

<?xml version="1.0" encoding="utf-8"?>
<contextxmlserver xmlns="http://pustefix.sourceforge.net/properties200401" xmlns:cus="http://www.schlund.de/pustefix/customize" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://pustefix.sourceforge.net/properties200401 http://pustefix.sourceforge.net/properties200401.xsd">

  <!-- Rest of configuration -->  

  <context defaultpage="EnterData">
    <resource class="org.pustefixframework.tutorial.firstapp.contextresources.ContextUserImpl">
      <implements class="org.pustefixframework.tutorial.firstapp.contextresources.ContextUser"/>
    </resource>
  </context>

  <!-- Rest of configuration -->  

</contextxmlserver>

2.6.5. Accessing a ContextResource

Now, that you have created a new context resource, you will learn, how you can access it from your applciation. All context resources can be accessed via the ContextResourceManager, which provides a getResource method.

ContextResourceManager manager = context.getContextResourceManager();
ContextUser cUser = manager.getResource(ContextUser.class);

This method accepts the Class object of the interface. If the resource is accessed for the first time, a new instance of the implementation specified in the servlet configuration, is created and returned. On subsequent calls, the same instance is returned, so the resource is a singleton in the session scope.

You can now implement the business logic in the handleSubmittedData method, that creates the User object and stores it in your context resource.

But first, you need to know, how you can extract the parameters from the request. Pustefix will pass two arguments to the handleSubmittedData method: The Context and an instance of IWrapper. This IWrapper actually is an instance of the generated EnterUserDataWrapper class, that has been generated from your wrapper configuration. This class provides getter-methods for all parameters that you specified in the wrapper. Use these methods to extract the data from the request and create a new User instance based on the input data:

package org.pustefixframework.tutorial.firstapp.handler;

import org.pustefixframework.tutorial.firstapp.User;
import org.pustefixframework.tutorial.firstapp.contextresources.ContextUser;
import org.pustefixframework.tutorial.firstapp.wrapper.EnterUserDataWrapper;

import de.schlund.pfixcore.generator.IHandler;
import de.schlund.pfixcore.generator.IWrapper;
import de.schlund.pfixcore.workflow.Context;
import de.schlund.pfixcore.workflow.ContextResourceManager;

public class EnterUserDataHandler implements IHandler {

    public void handleSubmittedData(Context context, IWrapper wrapper)
            throws Exception {
        ContextResourceManager manager = context.getContextResourceManager();
        ContextUser cUser = manager.getResource(ContextUser.class);
        
        EnterUserDataWrapper euWrapper = (EnterUserDataWrapper)wrapper;
        User user = new User();
        user.setSex(euWrapper.getSex());
        user.setName(euWrapper.getName());
        if (euWrapper.getEmail() != null) {
            user.setEmail(euWrapper.getEmail());
        }
        if (euWrapper.getHomepage() != null) {
            user.setHomepage(euWrapper.getHomepage());
        }
        if (euWrapper.getBirthdate() != null) {
            user.setBirthday(euWrapper.getBirthdate());
        }
        user.setAdmin(euWrapper.getAdmin());
        cUser.setUser(user);
    }

}

2.7. Implementing the workflow

Now you have implemented the business logic, that creates your User object and stores it in the session, but still, the EnterData page is displayed, after you submitted the page. The desired behaviour is to display the ReviewData after the business logic has successfully been executed.

To achieve this, you have to create a new workflow and specify the steps in this workflow. This is done via the <pageflow/> tag in the servlet configuration file:

<?xml version="1.0" encoding="utf-8"?>
<contextxmlserver xmlns="http://pustefix.sourceforge.net/properties200401" xmlns:cus="http://www.schlund.de/pustefix/customize" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://pustefix.sourceforge.net/properties200401 http://pustefix.sourceforge.net/properties200401.xsd">

  <!-- Rest of configuration -->  

  <pageflow name="RegisterUser" final="Confirm">
    <flowstep name="EnterData"/>
    <flowstep name="ReviewData" stophere="true"/>
  </pageflow>

  <!-- Rest of configuration -->  

</contextxmlserver>

The workflow starts with the EnterData page, which is followed by the ReviewData page. After the page flow is finished, the final page Confirm will be displayed. Open the EnterData page again, after you made the changes, fill out the mandatory fields and submit them: the page flow will lead you to the ReviewData page, as you specified in the configuration.

2.8. Displaying errors

Up to now, you expected, that the users of your application do not make any mistakes. We all know, that this is not the case for real-life users. To test, how the application reacts, if you do not fill out all required fields, open the EnterData page again, leave the name field empty and submit the page.

As you can see, Pustefix is clever enough not to continue the page flow. If you would debug the application, you would see, that Pustefix even does not execute the handleSubmittedData method of your handler. This is because the generated wrapper class does some basic validation on the input data. As you specified the name parameter as mandatory, the data is not valid and the wrapper is not passed to the handler.

But how is the user supposed to know, why the page flow is not processed, there is no error message. But this is not completely true. Pustefix sends you an error message, but your page does not display it. If you open the DOM-tree view again, you will see a difference in the XML document that is used for the page rendering:

<formresult serial="1214248690859">
      <formvalues>
          <param name="user.email">schst@bar.de</param>
          <param name="user.sex">m</param>
      </formvalues>
      <formerrors>
          <error name="user.name">
              <pfx:include href="core-override/dyntxt/statusmessages-core-merged.xml" part="MISSING_PARAM"/>
          </error>
      </formerrors>
      <formhiddenvals/>
      <wrapperstatus>
          <wrapper active="true" name="org.pustefixframework.tutorial.firstapp.wrapper.EnterUserDataWrapper" prefix="user"/>
      </wrapperstatus>
      <pageflow name="RegisterUser">
          <step current="true" name="EnterData"/>
          <step name="ReviewData"/>
      </pageflow>
  </formresult>

Inside the <formerrors/> node, there is an <error/> element which signals an error for the field user.name. The content of the error is an include to the part MISSING_PARAM in the file core-override/dyntxt/statusmessages-core-merged.xml. This file contains all error messages provided by the core. If you open the file and search for this part, you will find something ike this XML:

<part name="MISSING_PARAM">
  <theme name="default">This parameter is mandatory.</theme>
</part>

As you can see, Pustefix informs you, that an error happened, and even provides you with a human readable message for the error. All that is left for you to do, is display it to the user. This can be done using the <pfx:checkfield/>, <pfx:error/> and <pfx:scode/> tags provided by Pustefix.

To add the error message, open the page content file txt/pages/main_EnterData.xml. The <pfx:checkfield/> tag is used to check, whether an error happened for a specific field or not. Inside this tag, you can place a <pfx:error/> tag. The content of this tag will only be displayed, if an error occurred for this field. The <pfx:scode/> tag is an easy way to fetch the error message from the core-override/dyntxt/statusmessages-core-merged.xml file.

<?xml version="1.0" encoding="utf-8"?>
<include_parts xmlns:ixsl="http://www.w3.org/1999/XSL/Transform" xmlns:pfx="http://www.schlund.de/pustefix/core">
  <part name="content">
    <theme name="default">
      <h1>Register new user</h1>
      <pfx:forminput>
        <table>
          <!-- Other form fields -->
          <tr>
            <td>Name:</td>
            <td>
              <pfx:xinp type="text" name="user.name"/>
              <pfx:checkfield name="user.name">
                <pfx:error><div style="color:#ff0000;"><pfx:scode/></div></pfx:error>
              </pfx:checkfield>
            </td>
          </tr>
          <!-- Other form fields -->
        </table>
        <pfx:xinp type="submit" value="register"/>
      </pfx:forminput>
    </theme>
  </part>
 </include_parts>

If you now try to submit the page again without entering your name, Pustefix will display the correct error message. Of course, you have to repeat this step for all other input fields on your page.

2.9. Displaying the entered data

Now that your application is displaying errors correctly, you should go back to implement the main requirements. The next feature that you will be implementing is the review page, where the user will be able to review has data. To achieve this, you will have to write the user data to the DOM tree, that is used to render the page. This can be done by implementing the insertStatus method of the ContextUserImpl class.

This method receives the document and the element where it should append the user data. The ResultDocument class provides some wrapper methods, which make it easier to add data to the XML:

package org.pustefixframework.tutorial.firstapp.contextresources;

import org.pustefixframework.tutorial.firstapp.User;
import org.w3c.dom.Element;

import de.schlund.pfixcore.workflow.Context;
import de.schlund.pfixxml.ResultDocument;

public class ContextUserImpl implements ContextUser {

    public void insertStatus(ResultDocument document, Element element)
            throws Exception {
        if (this.user == null) {
            return;
        }
        ResultDocument.addTextChild(element, "sex", this.user.getSex());
        ResultDocument.addTextChild(element, "name", this.user.getName());
        ResultDocument.addTextChild(element, "email", this.user.getEmail());
        ResultDocument.addTextChild(element, "homepage", this.user.getHomepage());
        ResultDocument.addTextChild(element, "birthday", this.user.getBirthday());
        ResultDocument.addTextChild(element, "admin", String.valueOf(this.user.getAdmin()));
    }
}

Now that the context resource is able to render its state to the XML document, you have to tell Pustefix, on which pages, the context resource should be rendered. Again, this is done in the servlet configuration file app.conf.xml. This time, you will have to add an <output/> tag to the page and nest <resource/> tags for each context resource, that should be rendered on this page.

<?xml version="1.0" encoding="utf-8"?>
<contextxmlserver xmlns="http://pustefix.sourceforge.net/properties200401" xmlns:cus="http://www.schlund.de/pustefix/customize" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://pustefix.sourceforge.net/properties200401 http://pustefix.sourceforge.net/properties200401.xsd">

  <!-- other configuration options -->
  
  <pagerequest name="ReviewData">
    <output>
      <resource node="user" class="org.pustefixframework.tutorial.firstapp.contextresources.ContextUser"/>
    </output>
  </pagerequest>

</contextxmlserver>

The class attribute references the interface implemented by the context resource and the node attribute defines the name of the element in which it will be rendered.

Open the XML view for the page, after you made the changes and reloaded the page and you will see, that the user data has been inserted into the XML:

<formresult serial="1214249956789">
      <formvalues/>
      <formerrors/>
      <formhiddenvals/>
      <user>
          <sex>m</sex>
          <name>Stephan</name>
          <email>schst@bar.de</email>
          <homepage>http://pustefix-framework.org</homepage>
          <birthday>null</birthday>
          <admin>false</admin>
      </user>
      <pageflow name="RegisterUser">
          <step name="EnterData"/>
          <step current="true" name="ReviewData"/>
      </pageflow>
  </formresult>

Pustefix allows you to use XSL and XPath in your page definitions to insert this information in the rendered page. Use the ixsl namespace to access nodes from the DOM tree:

<?xml version="1.0" encoding="utf-8"?>
<include_parts xmlns:ixsl="http://www.w3.org/1999/XSL/Transform" xmlns:pfx="http://www.schlund.de/pustefix/core">
  <part name="content">
    <theme name="default">
      <h1>Review your data</h1>
      <table>
        <tr>
          <td>Gender:</td>
          <td>
            <ixsl:choose>
              <ixsl:when test="/formresult/user/gender/text() = 'f'">female</ixsl:when>
              <ixsl:otherwise>male</ixsl:otherwise>
            </ixsl:choose>
          </td>
        </tr>
        <tr>
          <td>Name:</td>
          <td><ixsl:value-of select="/formresult/user/name/text()"/></td>
        </tr>
        <tr>
          <td>Email:</td>
          <td><ixsl:value-of select="/formresult/user/email/text()"/></td>
        </tr>
        <tr>
          <td>Homepage:</td>
          <td><ixsl:value-of select="/formresult/user/homepage/text()"/></td>
        </tr>
        <tr>
          <td>Birthdate:</td>
          <td><ixsl:value-of select="/formresult/user/birthdate/text()"/></td>
        </tr>
        <tr>
          <td>Administrator:</td>
          <td><ixsl:value-of select="/formresult/user/admin/text()"/></td>
        </tr>
      </table>      
    </theme>
  </part>
 </include_parts>

If you now open the page again, enter your data and submit the form, Pustefix will display a new page, which contains the information you entered.

2.10. Saving the data

Your application now handles the input of user data and displays it again to the user. To complete the last requirement, your application will have to provide another button on the ReviewData page, which lets the user confirm the data and then executes the business logic to store the data in your persistence layer.

To achieve this, you will have to implement a new pair of wrapper and handler. As the ReviewData page does not contain any input fields, the new wrapper does not need any parameters:

<interface xsi:schemaLocation="http://pustefix.sourceforge.net/interfacewrapper200401 http://pustefix.sourceforge.net/interfacewrapper200401.xsd"
  xmlns="http://pustefix.sourceforge.net/interfacewrapper200401"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  
  <!-- This handler will process the data -->
  <ihandler class="org.pustefixframework.tutorial.firstapp.handler.SaveUserDataHandler"/>
</interface>

After you implemented the wrapper, continue by creating a new SaveUserData class, which implements the IHandler interface. Make sure that the handler is active by returning true from the prerequisitesMet and isActive methods. Place the business logic that saves the new user in the handleSubmittedData method. In this example, the business logic has been replaced by a simple System.out.println call.

package org.pustefixframework.tutorial.firstapp.handler;

import org.pustefixframework.tutorial.firstapp.contextresources.ContextUser;

import de.schlund.pfixcore.generator.IHandler;
import de.schlund.pfixcore.generator.IWrapper;
import de.schlund.pfixcore.workflow.Context;
import de.schlund.pfixcore.workflow.ContextResourceManager;

public class SaveUserDataHandler implements IHandler {

    public void handleSubmittedData(Context context, IWrapper wrapper)
            throws Exception {
        System.out.println("Business logic to save data");
    }

    public boolean isActive(Context context) throws Exception {
        return true;
    }

    public boolean needsData(Context context) throws Exception {
        return false;
    }

    public boolean prerequisitesMet(Context context) throws Exception {
        return true;
    }

    public void retrieveCurrentStatus(Context context, IWrapper arg1)
            throws Exception {
        // Nothing to be done here
    }
}

As the handler should be triggered from the ReviewData page, you have to add the handler to the <pagerequest/> tag in the servlet configuration:

<?xml version="1.0" encoding="utf-8"?>
<contextxmlserver xmlns="http://pustefix.sourceforge.net/properties200401"
  xmlns:cus="http://www.schlund.de/pustefix/customize"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"
  xsi:schemaLocation="http://pustefix.sourceforge.net/properties200401 http://pustefix.sourceforge.net/properties200401.xsd">
  
  <pagerequest name="ReviewData">
    <input>
      <interface prefix="user" class="org.pustefixframework.tutorial.firstapp.wrapper.SaveUserDataWrapper" />
    </input>
    <output>
      <resource node="user" class="org.pustefixframework.tutorial.firstapp.contextresources.ContextUser"/>
    </output>
  </pagerequest>

</contextxmlserver>

All that is left your you now, is to add a possibility to submit the ReviewData page. But as there is no input form needed, you will learn a new way, how data can be submitted using Pustefix. The <pfx:button/> tag is used, to create links between different Pustefix pages. But it can also be used to link to the same page again and pass any arguments using the <pfx:argument/>. If you use the <pfx:argument/> tag, Pustefix will treat the request as if the page had been submitted and call the handleSubmittedData method on all handlers on the page.

As your handler does not accept any parameters, you can pass any argument as you like, for example setting the argument user.save to true.

<?xml version="1.0" encoding="utf-8"?>
<include_parts xmlns:ixsl="http://www.w3.org/1999/XSL/Transform" xmlns:pfx="http://www.schlund.de/pustefix/core">
  <part name="content">
    <theme name="default">
      <h1>Review your data</h1>
      <!-- Display user data -->
      <pfx:button>
        <pfx:argument name="user.save">true</pfx:argument>
        Go ahead and save the data
      </pfx:button>
    </theme>
  </part>
 </include_parts>

If you click on this link, the page will be submitted and if you placed the business logic inside the handler, it will be executed.

Open the txt/pages/main_Confirm.xml file and add some HTML, that will be displayed after the Pustefix workflow will continue to the last page.

<?xml version="1.0" encoding="utf-8"?>
<include_parts xmlns:ixsl="http://www.w3.org/1999/XSL/Transform" xmlns:pfx="http://www.schlund.de/pustefix/core">
  <part name="content">
    <theme name="default">
      <h1>Congratulations</h1>
      <p>Your data has been saved</p>
    </theme>
  </part>
 </include_parts>

Now you have implemented all specified requirements and finished your first Pustefix application.

In the last part of the tutorial you will polish some of the rough edges of your applications, which you did not deal with while implementing the application.

2.11. The finishing touch

Although your application is already working, there are some minor problems that you should deal with.

2.11.1. Changing data

In a typical application, the review page does not only display the entered data, but offer you a link to go back and modify the data you entered before. Your application currently lacks this feature, but you will now learn how this is implemented in Pustefix.

At first, you have to add a new link to the main_ReviewData.xml page, which sends the user back to the EnterData page. This can be done using the <pfx:button/> tag:

<pfx:button page="EnterData">Go back and edit data</pfx:button>

If you click on this link, the EnterData page will be loaded and the form will be displayed again. But the application does not behave exactly as you would like it to, as the form fields are empty and not filled with the data that the user entered before. This can be easily changed.

Every time a page is rendered, Pustefix will call the retrieveCurrentStatus method of all handlers that are registered for the page and pass in the context and the matching wrapper for the handler. In this method you may use the generated setter methods of the wrapper class to assign values to the form fields.

To prefill the form fields with the values the user entered before, you need to fetch the User instance from the context resource, extract the properties and assign them to the form elements. This is almost the opposite of what you implemented in the handleSubmittedData method:

  • Fetch the ContextUser context resource using the ContextResourceManager.

  • Fetch the User instance from the context resource.

  • If the user equals null the page is accessed for the first time, no user data has been entered, and there is nothing left to do. You can leave the method using the return statement.

  • Cast the generic wrapper parameter to the concrete EnterUserDataWrapper implementation.

  • Get the properties from the User instance and set them to the corresponding properties in the wrapper.

package org.pustefixframework.tutorial.firstapp.handler;

import org.pustefixframework.tutorial.firstapp.User;
import org.pustefixframework.tutorial.firstapp.contextresources.ContextUser;
import org.pustefixframework.tutorial.firstapp.wrapper.EnterUserDataWrapper;

import de.schlund.pfixcore.generator.IHandler;
import de.schlund.pfixcore.generator.IWrapper;
import de.schlund.pfixcore.workflow.Context;
import de.schlund.pfixcore.workflow.ContextResourceManager;

public class EnterUserDataHandler implements IHandler {

    public void retrieveCurrentStatus(Context context, IWrapper wrapper)
            throws Exception {
        ContextResourceManager manager = context.getContextResourceManager();
        ContextUser cUser = manager.getResource(ContextUser.class);
        User user = cUser.getUser();
        if (user == null) {
            return;
        }
        EnterUserDataWrapper euWrapper = (EnterUserDataWrapper)wrapper;

        euWrapper.setSex(user.getSex());
        euWrapper.setName(user.getName());
        euWrapper.setEmail(user.getEmail());
        euWrapper.setHomepage(user.getHomepage());
        euWrapper.setBirthdate(user.getBirthday());
        euWrapper.setAdmin(user.getAdmin());
    }
}

If you restart the application, enter some data and then go back to the form, you can see, that the form fields contain the values that you wanted them to contain and that you entered before.

2.11.2. Avoiding unwanted access

Another problem ist, that your pages are not protected against unwanted access. If you open the URL http://first-app.HOSTNAME.DOMAIN/xml/app/ReviewData, Pustefix will display this page, although there is not data to review.

It is the responsibility of the application developer to make sure, that the page can only be displayed, if it makes sense in the application context. However, Pustefix provides an easy way to disable the availability of a page. The method prerequisitesMet will be called on every handler, every time a page should be displayed. If this method returns false, the page will not be displayed.

To protect the ReviewData page, you have to modify the prerequisitesMet method of the SaveUserDataHandler handler. As the page should only be accessible, if a user has been entered, you only need to fetch the ContextUser context resource and check, whether it contains user data. If no user has been set, the method must return false, otherwise it must return true.

package org.pustefixframework.tutorial.firstapp.handler;

import org.pustefixframework.tutorial.firstapp.contextresources.ContextUser;

import de.schlund.pfixcore.generator.IHandler;
import de.schlund.pfixcore.generator.IWrapper;
import de.schlund.pfixcore.workflow.Context;
import de.schlund.pfixcore.workflow.ContextResourceManager;

public class SaveUserDataHandler implements IHandler {

    public boolean prerequisitesMet(Context context) throws Exception {
        ContextResourceManager manager = context.getContextResourceManager();
        ContextUser cUser = manager.getResource(ContextUser.class);
        if (cUser.getUser() != null) {
            return true;
        }
        return false;
    }
}

If you now open the http://first-app.HOSTNAME.DOMAIN/xml/app/ReviewData, the page cannot be displayed. Now the workflow mechanism kicks in and selects the next page, that should be displayed. This is the Confirm page, which has been defined as the final page of the workflow.

It would have been better, if the EnterData page would have been displayed instead of the Confirm, page, as the user must enter data before any other page can be displayed. This can easily be achieved using Pustefix.

Each handler has a needsData method, which will be called, when the workflow mechanism selects the next page to display. The workflow will start with the first page in the current workflow and if the needsData method returns true, it will stop at this page until the handler is satisfied.

To make sure, that the user will stay on this page, you only need to implement the needsData method of the EnterUserDataHandler handler: the method must return true, unless the context resource has a User instance.

package org.pustefixframework.tutorial.firstapp.handler;

import org.pustefixframework.tutorial.firstapp.User;
import org.pustefixframework.tutorial.firstapp.contextresources.ContextUser;
import org.pustefixframework.tutorial.firstapp.wrapper.EnterUserDataWrapper;

import de.schlund.pfixcore.generator.IHandler;
import de.schlund.pfixcore.generator.IWrapper;
import de.schlund.pfixcore.workflow.Context;
import de.schlund.pfixcore.workflow.ContextResourceManager;

public class EnterUserDataHandler implements IHandler {

    public boolean needsData(Context context) throws Exception {
        ContextResourceManager manager = context.getContextResourceManager();
        ContextUser cUser = manager.getResource(ContextUser.class);
        if (cUser.getUser() == null) {
            return true;
        }
        return false;
    }
}

if you now try to access the ReviewData page directly, Pustefix will redirect you to the EnterData page instead until you entered the data of a new user.

2.12. Conclusion

In this tutorial you learned the basics about the core features of Pustefix and developed a very simple, but typical, web application. For details about the features that you got to know in this tutorial, take a look at the comprehensive reference documentation, which describes all the configuration options, XML tags and interfaces in detail.

Of course, Pustefix provides features to automate these tasks, there is no need to re-implement everything from scratch for your applications. See Chapter 3, Usermanager tutorial to learn how Pustefix will automatically generate wrapper definitions from your existing beans, how to add custom validations and how you can avoid working with DOM and adding any object structure to the generated XML tree automatically.

Usermanager tutorial

Tobias Fehrenbach