Table of Contents
List of Figures
List of Tables
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.
Table of Contents
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.
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.
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.
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 PFX_ANT_LIB
that contains the path to the lib/ant.jar
within your
Ant installation directory.
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.
Table of Contents
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:
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.
Validate the user data after the page has been submitted and display error information.
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.
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.
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.
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
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
.
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.
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"?> <context-xml-service-config xmlns="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config ../../core/schema/context-xml-service-config.xsd"> <global-config /> <context defaultpage="EnterData" /> <pagerequest name="home1"/> </context-xml-service-config>
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:
Delete the file main_home1.xml
from the first-app/txt/pages
folder.
Remove the <pagerequest/>>
tag from the app.conf.xml
file.
Remove the <page/>>
and <standardpage/>>
tags from the depend.xml
file.
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.
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.
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:
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).
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.
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:
class
specifies the clasname of the wrapper.
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"?> <context-xml-service-config xmlns="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config ../../core/schema/context-xml-service-config.xsd"> <!-- ... --> <pagerequest name="EnterData"> <input> <wrapper prefix="user" class="org.pustefixframework.tutorial.firstapp.wrapper.EnterUserDataWrapper" /> </input> </pagerequest> </context-xml-service-config>
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.
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.
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:
It has to implement the de.schlund.pfixcore.generator.IHandler
interface.
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
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">[email protected]</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.
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:
There has to be an interface and an implementation for the context resource.
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"?> <context-xml-service-config xmlns="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config ../../core/schema/context-xml-service-config.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 --> </context-xml-service-config>
Now, that you have created a new context resource, you will learn, how you can access it from your application.
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); } }
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"?> <context-xml-service-config xmlns="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config ../../core/schema/context-xml-service-config.xsd"> <!-- Rest of configuration --> <pageflow name="RegisterUser" final="Confirm"> <flowstep name="EnterData"/> <flowstep name="ReviewData" stophere="true"/> </pageflow> <!-- Rest of configuration --> </context-xml-service-config>
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.
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">[email protected]</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.
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"?> <context-xml-service-config xmlns="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config ../../core/schema/context-xml-service-config.xsd"> <!-- other configuration options --> <pagerequest name="ReviewData"> <output> <resource node="user" class="org.pustefixframework.tutorial.firstapp.contextresources.ContextUser"/> </output> </pagerequest> </context-xml-service-config>
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>[email protected]</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.
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"?> <context-xml-service-config xmlns="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config ../../core/schema/context-xml-service-config.xsd"> <pagerequest name="ReviewData"> <input> <wrapper prefix="user" class="org.pustefixframework.tutorial.firstapp.wrapper.SaveUserDataWrapper" /> </input> <output> <resource node="user" class="org.pustefixframework.tutorial.firstapp.contextresources.ContextUser"/> </output> </pagerequest> </context-xml-service-config>
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.
Although your application is already working, there are some minor problems that you should deal with.
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.
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.
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.
Table of Contents
In this tutorial you will learn how to work with the advanced feature provided by the Pustefix Framework. Like in the basic tutorial you will accept user input, store it in the session and display the data. Furthermore you will be able to delete and edit the data stored in the session.
The requirements for your application are:
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.
Validate the user data after the page has been submitted and display error information.
If the entered data is coorect, move to an overview page, which displays a list of user data and allows the user to delete, edit or add new users.
If you edit the user data, the page with the HTML form shows up with prefilled values.
If you choose to delete the user data the data will be removed from session context.
To build this tutorial, create a new project usermanagement
as described in
Section 2.1, “Setting up a new project” with the following data:
Property | Value |
---|---|
Project name | usermanagement |
Project language | Use the default value |
Project comment | User management tutorial |
Servlet name | user |
Create a simple Java-bean User
in the package
org.pustefixframework.tutorial.usermanagement
where you can store your user user data.
package org.pustefixframework.tutorial.usermanagement; import java.net.URL; import java.util.Date; public class User { private Integer id; private String name; private String email; private Date birthday; private Boolean admin; private URL homepage; private String gender; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } 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 Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public Boolean getAdmin() { return admin; } public void setAdmin(Boolean admin) { this.admin = admin; } public URL getHomepage() { return homepage; } public void setHomepage(URL homepage) { this.homepage = homepage; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } }
Create a new class UserHandler
which implements
de.schlund.pfixcore.generator.IHandler
package org.pustefixframework.tutorial.usermanagement; import de.schlund.pfixcore.generator.IHandler; import de.schlund.pfixcore.generator.IWrapper; import de.schlund.pfixcore.workflow.Context; public class UserHandler implements IHandler { public void handleSubmittedData(Context context, IWrapper wrapper) throws Exception { } 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 wrapper) throws Exception { } }
In order to cast the String parameter from the HTML form to the java.net.URL
object
we defined in the bean above, you need to create a caster.
First of all you need a new statuscode to show an appropriate errormessage. Since this is your first statuscode you have to setup this first.
Create the file projects/common/dyntxt/statuscodeinfo.xml
with the following content.
<?xml version="1.0" encoding="UTF-8"?> <statuscodeinfo xmlns="http://pustefix-framework.org/statuscodeinfo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pustefix-framework.org/statuscodeinfo ../../core/schema/statuscodeinfo.xsd"> <statuscodes class="org.pustefixframework.tutorial.StatusCodeLib"> <file>statusmessages.xml</file> </statuscodes> </statuscodeinfo>
This configuration file tells you application that the statuscodes can be found in the file
statusmessages.xml
in the same directory
You need to create the file projects/common/dyntxt/statusmessages.xml
and add an statuscode
for an invalid url.
<?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="caster.url.INVALID"> <theme name="default"> The URL you entered is not valid </theme> </part> </include_parts>
Create a new class ToURL
in the package org.pustefixframework.tutorial.caster
which extends SimpleCheck
.
The ToURL
class casts the request param to an java.net.URL
object and adds the new
created statuscode if the url is malformed.
package org.pustefixframework.tutorial.caster; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.pustefixframework.tutorial.StatusCodeLib; import de.schlund.pfixcore.generator.IWrapperParamCaster; import de.schlund.pfixcore.generator.SimpleCheck; import de.schlund.pfixxml.RequestParam; import de.schlund.util.statuscodes.StatusCode; public class ToURL extends SimpleCheck implements IWrapperParamCaster { private URL[] value = null; private StatusCode scode; public ToURL() { scode = StatusCodeLib.CASTER_URL_INVALID; } public void castValue(RequestParam[] requestParams) { List<URL> out = new ArrayList<URL>(); URL url; for (RequestParam param : requestParams) { try { url = new URL(param.getValue()); out.add(url); } catch(MalformedURLException ex) { addSCode(scode); } } if (!errorHappened()) { value = out.toArray(new URL[] {}); } } public Object[] getValue() { return value; } }
Instead of creating an iwrp
file described in Section 2.6.1, “Implementing a wrapper”
you can now use annotations to create the wrapper class. Therefor you need to annotate the class and the getters of
the user bean from above.
To tell the pustefix build system to create an wrapper class from the bean you need to add the @IWrapper
annotation to the class declaration. You can specify the name of the generated wrapper class and the corresponding
handler class.
import de.schlund.pfixcore.generator.annotation.IWrapper;
@IWrapper(name="UserWrapper", ihandler=UserHandler.class) public class User { ...
The @Param annotation is used to mark a bean property as parameter. You can select the name of the parameter and choose if it is mandatory or optional.
import de.schlund.pfixcore.generator.annotation.Caster; import de.schlund.pfixcore.generator.annotation.Param; import de.schlund.pfixcore.generator.annotation.Transient; import de.schlund.pfixcore.oxm.impl.annotation.DateSerializer; import org.pustefixframework.tutorial.caster.ToURL;
The @Transient
annotation is used to avoid the generation of a wrapper parameter.
@Transient public int getId() { ...
@Param(name="name", mandatory=true) public String getName() { ...
@Param(name="email", mandatory=true) public String getEmail() { ...
The @DateSerializer("format")
annotation tells the wrapper that it should use the given serializer
class to serialize this property.
@Param(name="birthday", mandatory=true) @DateSerializer("yyyy/MM/dd") public Date getBirthday() { ...
@Param(name="admin", mandatory=false) public boolean getAdmin() { ...
The @Caster
annotation notes the caster implementation class. Here you add your ToURL caster
implementation from Section 3.4, “Create a caster”.
@Param(name="homepage", mandatory=false) @Caster(type=ToURL.class) public URL getHomepage() { ...
@Param(name="gender", mandatory=true) public String getGender() { ...
Type ant generate-src to generate the wrapper and the StatusCodeLib class.
After ant
finishes successful you will find the new classes
org.pustefixframework.tutorial.usermanagement.UserWrapper
and
org.pustefixframework.tutorial.StatusCodeLib
in the gensrc
directory of your
installation.
You need a class to hold your users. Therefor create a new class
org.pustefixframework.tutorial.usermanagement.UserList
with the property users and methods to
get one specific users, get all users and add a new user. Further methods will be added later.
package org.pustefixframework.tutorial.usermanagement; import java.util.ArrayList; import java.util.List; public class UserList { private List<User> users = new ArrayList<User>(); private int id = 0; public void addUser(User user) { user.setId(id); users.add(user); id++; } public List<User> getUsers() { return users; } public User getUser(int id) { for (User user : users) { if (user.getId() == id) { return user; } } return null; } }
Get rid of the old sample stuff by removing the sample pageflow, the pagerequest elements and the defaultflow attribute of the context element
Add this to the servlet configuration file user.conf.xml
.
The context element should look like the following snippet:
<context> <resource class="org.pustefixframework.tutorial.usermanagement.UserList" /> </context>
Adding a new user is quite simple. Implement the handleSubmittedData method of the UserHandler
you created in Section 3.3, “Create the handler”. You create a new User
object by calling
IWrapperToBean.createBean(IWrapper iwrapper, Class<T> clazz)
, get the
UserList
from session context and add the user to the userList
instance.
import de.schlund.pfixcore.generator.iwrpgen.IWrapperToBean;
User user = IWrapperToBean.createBean(wrapper, User.class); UserList userList = context.getContextResourceManager().getResource(UserList.class); userList.addUser(user);
Here you create two pages: One page for the user data input and one page to list the user data. To keep this
application clean get rid of the sample pages by removing the sample page main_home1.xml
in the directory projects/usermanagement/txt/pages
Create a new file main_userform.xml
in the directory
projects/usermanagement/txt/pages
. This page will contain the HTML form to submit the user data.
<?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"> <pfx:forminput> Name: <pfx:xinp type="text" name="user.name" /> <pfx:checkfield name="user.name"> <pfx:error><pfx:scode/></pfx:error> </pfx:checkfield> <br/> E-Mail: <pfx:xinp type="text" name="user.email" /> <pfx:checkfield name="user.email"> <pfx:error><pfx:scode/></pfx:error> </pfx:checkfield> <br/> Birthday: <pfx:xinp type="text" name="user.birthday" /> <pfx:checkfield name="user.birthday"> <pfx:error><pfx:scode/></pfx:error> </pfx:checkfield> <br/> Gender: <pfx:xinp type="select" name="user.gender"> <pfx:option value="m" >male</pfx:option> <pfx:option value="f" >female</pfx:option> </pfx:xinp> <pfx:checkfield name="user.gender"> <pfx:error><pfx:scode/></pfx:error> </pfx:checkfield> <br/> Homepage: <pfx:xinp type="text" name="user.homepage" /> <pfx:checkfield name="user.homepage"> <pfx:error><pfx:scode/></pfx:error> </pfx:checkfield> <br/> Admin: <pfx:xinp type="check" name="user.admin" value="true" default="false" /> <br/> <pfx:xinp type="submit" value="save"/> </pfx:forminput> </theme> </part> </include_parts>
Create a new file main_overview.xml
in the directory
projects/usermanagement/txt/pages
. This page shows all users which were stored in the
session context. Later this page will provide links to delete
and edit
users.
<?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"> <h3>Users</h3> <ixsl:for-each select="/formresult/users/users/user"> Name: <ixsl:value-of select="./@name"/><br/> Email: <ixsl:value-of select="./@email"/><br/> Birthday: <ixsl:value-of select="./@birthday"/><br/> Gender: <ixsl:value-of select="./@gender"/><br/> <ixsl:if test="./@homepage"> Homepage: <ixsl:value-of select="./@homepage"/><br/> </ixsl:if> Admin: <ixsl:value-of select="./@admin"/><br/> <br/><br/> </ixsl:for-each> <pfx:button page="userform" >add</pfx:button> </theme> </part> </include_parts>
Add two new pagerequest
elements to the user.conf.xml
configuration file below the context
element.
<pagerequest name="userform"> <input> <wrapper prefix="user" class="org.pustefixframework.tutorial.usermanagement.UserWrapper" /> </input> </pagerequest>
<pagerequest name="overview"> <output> <resource node="users" class="org.pustefixframework.tutorial.usermanagement.UserList" /> </output> </pagerequest>
Set the overview
page as defaultpage
by adding the
attribute to the context element as described in Section 2.4, “Setting the entry page”.
<context defaultpage="overview">
Remove the sample page element and the sample standardpage alement.
Add the following page element within the navigation element.
<page handler="/xml/user" name="userform"/> <page handler="/xml/user" name="overview"/>
and this after the standardmetatags element:
<standardpage name="userform" xml="usermanagement/xml/frame.xml"/> <standardpage name="overview" xml="usermanagement/xml/frame.xml"/>
Place a new pageflow
element between context
and
pagerequest
elements in the servlet configuration file user.conf.xml
.
<pageflow name="userFlow" final="overview"> <flowstep name="userform"/> <flowstep name="overview"/> </pageflow>
Type ant clean all. When ant
is finished you can start
Tomcat
by typing ./startTomcat.sh
Now you can point your browser to http://usermanagement.yourhost.yourdomain:8080
If everything works fine you should see this
If you tried out your application you've seen that the overview page is empty unless you add new users.
You can add some sample users by calling the addUser
method of
UserList
directly.
To add sample users more easily add an constructor to set the mandatory data at once.
public User(String name, String email, Date birthday, Boolean admin, URL homepage, String gender) { this.name = name; this.email = email; this.birthday = birthday; this.admin = admin; this.homepage = homepage; this.gender = gender; }
Further you need a default constructor here.
public User() {
}
To add the users when the context resource
is initialized you need to add a method annotated with
@InitResource
.
import java.util.GregorianCalendar; import java.net.URL; import de.schlund.pfixcore.beans.InitResource;
@InitResource public void createSampleUsers() throws Exception { addUser(new User("Neo", "[email protected]", new GregorianCalendar(1964, 8, 2).getTime(), true, new URL("http://pustefix-framework.org"), "m")); addUser(new User("Trinity", "[email protected]", new GregorianCalendar(1967, 7, 21).getTime(), true, new URL("http://pustefix-framework.org"), "f")); addUser(new User("Morpheus", "[email protected]", new GregorianCalendar(1961, 6, 30).getTime(), true, new URL("http://pustefix-framework.org"), "m")); }
When you try this out again (see Section 3.12, “Try to add your own user”) you should see the following screen:
To remove an existing user from the UserList
you need to create a new bean which stores
the selected user id, a new handler and a delete method in the UserList
class.
Create another bean org.pustefixframework.tutorial.usermanagement.DeleteUser
. This bean
stores the id of the selected user.
package org.pustefixframework.tutorial.usermanagement; public class DeleteUser { private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } }
Create a new handler org.pustefixframework.tutorial.usermanagement.DeleteUserHandler
. This
handler gets the selected user id from the wrapper and calls the remove method from UserList
.
package org.pustefixframework.tutorial.usermanagement; import de.schlund.pfixcore.generator.IHandler; import de.schlund.pfixcore.generator.IWrapper; import de.schlund.pfixcore.workflow.Context; public class DeleteUserHandler implements IHandler { public void handleSubmittedData(Context context, IWrapper wrapper) throws Exception { } 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 wrapper) throws Exception { } }
import de.schlund.pfixcore.generator.annotation.IWrapper;
@IWrapper(name="DeleteUserWrapper", ihandler=DeleteUserHandler.class) public class DeleteUser {
Add a new method deleteUser(Integer id)
to remove an existing user from
UserList
.
public void deleteUser(Integer id) { User userToDelete = null; for (User user: users) { if (user.getId() == id) { userToDelete = user; } } if (userToDelete != null) { users.remove(userToDelete); } }
To delete an user implement the method handleSubmittedData
of
DeleteUserHandler
. First create a new DeleteUser
bean with
IWrapperToBean.createBean(Iwrapper wrapper, Class<T>
and get your
UserList
instance from session context
. Then call the
deleteUser(Integer id)
method.
import de.schlund.pfixcore.generator.iwrpgen.IWrapperToBean;
public void handleSubmittedData(Context context, IWrapper wrapper) throws Exception { DeleteUser deleteUser = IWrapperToBean.createBean(wrapper, DeleteUser.class); UserList userList = context.getContextResourceManager().getResource(UserList.class); userList.deleteUser(deleteUser.getId()); }
You need a button to trigger to deletion of a user. Simply add the following code inside the for-each
loop after the admin text in the main_overview.xml
file
<pfx:button>delete <pfx:command name="SELWRP">delete</pfx:command> <pfx:argument name="delete.id"><ixsl:value-of select="./@id"/></pfx:argument> </pfx:button>
The last feature in this tutorial is to edit an existing user
Create another bean org.pustefixframework.tutorial.usermanagement.EditUser
. This bean
stores the id of the selected user.
package org.pustefixframework.tutorial.usermanagement; public class EditUser { private Integer id; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } }
Create a new handler org.pustefixframework.tutorial.usermanagement.EditUserHandler
.
This handler gets the selected user id from the wrapper and loads the selected user.
package org.pustefixframework.tutorial.usermanagement; import de.schlund.pfixcore.generator.IHandler; import de.schlund.pfixcore.generator.IWrapper; import de.schlund.pfixcore.workflow.Context; public class EditUserHandler implements IHandler { public void handleSubmittedData(Context context, IWrapper wrapper) throws Exception { } 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 wrapper) throws Exception { } }
To generate a wrapper
from this bean you have to annotate it.
import de.schlund.pfixcore.generator.annotation.IWrapper;
@IWrapper(name="EditUserWrapper", ihandler=EditUserHandler.class) public class EditUser {
To edit an user you need to implement the method to replace an existing user.
We add the following code to the UserList
class.
public void replaceUser(User user) { User userToReplace = null; for (User existingUser : users) { if (existingUser.getId().equals(user.getId())) { userToReplace = existingUser; } } if (userToReplace != null) { users.remove(userToReplace); users.add(user); } }
To edit an user implement the method handleSubmittedData
of
EditUserHandler
. First create a new EditUser
bean with
IWrapperToBean.createBean(Iwrapper wrapper, Class<T>
and get your
EditUser
instance from session context
. Then call the
setId(Integer id)
method. This stores the id of the user in the session context.
import de.schlund.pfixcore.generator.iwrpgen.IWrapperToBean;
public void handleSubmittedData(Context context, IWrapper wrapper) throws Exception { EditUser editUser = IWrapperToBean.createBean(wrapper, EditUser.class); EditUser editUserContext = context.getContextResourceManager().getResource(EditUser.class); editUserContext.setId(editUser.getId()); }
We need a button to edit an user. We add the following code below the delete button in the
main_overview.xml
file.
<pfx:button jumptopage="userform">edit <pfx:command name="SELWRP">edit</pfx:command> <pfx:argument name="edit.id"><ixsl:value-of select="./@id"/></pfx:argument> </pfx:button>
Add the EditUser
class as an resource to the context element
<resource class="org.pustefixframework.tutorial.usermanagement.EditUser" />
Add the following interface
element into the input
element of the
pagerequest
of the overview page.
<wrapper prefix="edit" class="org.pustefixframework.tutorial.usermanagement.EditUserWrapper" />
To add the user data to the existing form of the userform
page you need to implement the
retrieveCurrentStatus method of your UserHandler
class.
import de.schlund.pfixcore.generator.iwrpgen.BeanToIWrapper;
public void retrieveCurrentStatus(Context context, IWrapper wrapper) throws Exception { EditUser editUser = context.getContextResourceManager().getResource(EditUser.class); if (editUser.getId() != null) { UserList userList = context.getContextResourceManager().getResource(UserList.class); User user = userList.getUser(editUser.getId()); BeanToIWrapper.populateIWrapper(user, wrapper); } }
To add and replace an existing user you have to edit the method handleSubmittedData
:
public void handleSubmittedData(Context context, IWrapper wrapper) throws Exception { UserList userList = context.getContextResourceManager().getResource(UserList.class); EditUser editUser = context.getContextResourceManager().getResource(EditUser.class); User user = IWrapperToBean.createBean(wrapper, User.class); if (editUser.getId() != null) { // replace existing user user.setId(editUser.getId()); userList.replaceUser(user); editUser.setId(null); } else { // add new user userList.addUser(user); } }
In this tutorial you learned how to automatically create wrapper from annotated java 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. 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 annotations, configuration options, XML tags and interfaces in detail.
Table of Contents
In this tutorial you will learn how to create an AJAX application with Pustefix. As an example, you will build a very simple calculator, which does the real calculation in Java on the server. As in the other tutorials, the business logic has been kept extremely simple to focus on the tasks that are required to implement an AJAX application with the Pustefix framework.
No matter how complex your business logic is, the tasks required to set up an AJAX application will be almost the same as in this tutorial.
To build this tutorial, create a new project ajax-caculator
with one servlet as described in
Section 2.1, “Setting up a new project” with the following data:
Property | Value |
---|---|
Project name | ajax-calculator |
Project language | Use the default value |
Project comment | AJAX Calculator tutorial |
Servlet name | calc |
Although you are building an AJAX application and will not need any wrappers and handlers which process your submit requests, you
still need at least one HTML page as a starting point for your application. Please create a page called Calculator
by adding the necessary tags to the depend.xml
as described in Section 2.3, “Creating the pages”. As the pages
itself do not trigger any business logic, there is no need to make the page known in the servlet configuration file.
Set the new Calculator
page as the default page as described in Section 2.4, “Setting the entry page”
In AJAX applications, you will need a servlet, that is able to process the AJAX requests sent by the client.
Pustefix provides a ready-to-use servlet, that is able to expose any Java class as a webservice and process
requests made by the client. To use this servlet, open the project.xml
configuration
file and add the the webservice element at the end of the file.
<?xml version="1.0" encoding="utf-8"?> <project-config xmlns="http://www.pustefix-framework.org/2008/namespace/project-config"> <!-- These lines have been generated --> <project> <name>ajax-calculator</name> <description>AJAX calculator tutorial</description> </project> <editor> <enabled>true</enabled> <location>http://cms.${fqdn}/</location> </editor> <xml-generator> <config-file>pfixroot:/ajax-calculator/conf/depend.xml</config-file> </xml-generator> <http-server> <http-port> <address>${fqdn}</address> </http-port> <!-- we do not use SSL here <https-port>--> <address>${fqdn}</address> <ssl-key>pfixroot:/ajax-calculator/conf/server.key</ssl-key> <ssl-crt>pfixroot:/ajax-calculator/conf/server.crt</ssl-crt> </https-port> --> <server-name>ajax-calculator.${fqdn}</server-name> <server-alias>ajax-calculator.${machine}</server-alias> <apache> <literal> <!-- Stuff that is copied to the Apache configuration --> </literal> </apache> <tomcat> <enable-extra-webapps>false</enable-extra-webapps> </tomcat> </http-server> <application> <docroot-path>pfixroot:/ajax-calculator/htdocs</docroot-path> <default-path>/xml/calc</default-path> <context-xml-service> <path>/xml/calc</path> <config-file>pfixroot:/ajax-calculator/conf/calc.conf.xml</config-file> </context-xml-service> <static> <path>ajax-calculator/img</path> </static> <!-- Add these lines manually --> <webservice-service> <path>/xml/webservice</path> <config-file>pfixroot:/ajax-calculator/conf/webservice.conf.xml</config-file> </webservice-service> </application> </project-config>
Instead of using the ContextXMLServlet
for the servlet, you need to use the
org.pustefixframework.webservices.WebServiceServlet
instead (In version 0.12.2 you need to use de.schlund.pfixcore.webservice.WebServiceServlet
.). To configure the webservice servlet
you add the ajax-calculator/conf/webservice.conf.xml
file in the <propfile/>
tag.
This webservice.conf.xml
file makes use of the XML syntax like all the previous configuration files
you encountered in Pustefix. Add the following content to the file:
<webservice-config xmlns="http://www.pustefix-framework.org/2008/namespace/webservice-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/webservice-config ../../core/schema/webservice-config.xsd"> <webservice-global> <requestpath>/xml/webservice</requestpath> <wsdlsupport enabled="false"/> <stubgeneration enabled="false"/> <protocol type="JSONWS"/> <session type="servlet"/> <scope type="application"/> <admin enabled="true"/> <monitoring enabled="true" scope="session" historysize="10"/> <logging enabled="true"/> <faulthandler class="org.pustefixframework.webservices.fault.LoggingHandler"/> </webservice-global>
Table 4.1, “Global webservice configuration ” lists all used configuration options and describes their usage in this example.
Option | Description |
---|---|
requestpath |
The path where the webservice requests will be sent to. |
wsdlsupport |
Whether WSDL for SOAP webservices should be generated. As you will be using a JSON protocol instead of SOAP, this is not needed. |
stubgeneration |
Whether JavaScript stub classes should be generated in the build process. Again, this is not needed when using a JSON-based protocol. |
protocol |
The protocol to use. In this example, you will be using the JSONWS protocol, which is very similar to JSON-RPC.
|
session |
Which session type to use. Using servlet means that there must be an HTTP-session that can be used in your services.
|
scope |
Specifies the scope in which your web services will be executed. When using application , the service object is created only once and reused for all requests.
|
admin |
Whether to enable the web service admin console. |
monitoring |
Whether to enable the web service monitoring, which is useful for debgging. |
logging |
Whether to enable the pustefix-webservice.log, which contains all requests and responses. |
faulthandler |
Specifies a class that will receive all exceptions that are thrown in your web services. |
You have now configured the WebServiceServlet
and it is now available at
http://ajax-calculator.HOSTNAME.DOMAIN.de/xml/webservice/
. If you open this URL in your
browser, you will get an HTTP Code 400 Bad Request
as you made a simple HTTP
request instead of a webservice request.
To make web service requests, you will at first have to implement a service that you can send requests to.
In Pustefix, a web service always has to consist of a service interface and an implementation. In this tutorial, you will implement a very simple webservice which provides three methods to execute mathematical operations.
Add two integer numbers
Subtract two integer numbers
Multiply two integer numbers
The interface for this business logic is very easy to implement:
package org.pustefixframework.tutorial.calculator; public interface CalculatorService { public int add(int a, int b); public int subtract(int a, int b); public int multiply(int a, int b); }
The implementation for this interface is not any harder to implement than the interface iteself:
package org.pustefixframework.tutorial.calculator; import org.pustefixframework.webservices.AbstractService; public class CalculatorServiceImpl extends AbstractService implements CalculatorService { public int add(int a, int b) { return a+b; } public int subtract(int a, int b) { return a-b; } public int multiply(int a, int b) { return a*b; } }
As you can see, the CalculatorServiceImpl
implements the
CalculatorService
service interface and extends
the abstract class AbstractService
provided by Pustefix.
All of your web service implementations should extend this class to inherit some methods
that are useful when working with web services.
Accessing the context | |
---|---|
The |
Now that you have defined the interface for your service and also provided an implementation for it, the next
step is to expose this service using the WebServiceServlet
provided by Pustefix
This is done by adding some additional configuration options to the webservice.conf.xml
file
you created earler (in Section 4.2, “Using the webservice servlet”). For each webservice you want to expose, you
have to add one <webservice/>
tag in this configuration file:
<webservice-config xmlns="http://www.pustefix-framework.org/2008/namespace/webservice-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/webservice-config ../../core/schema/webservice-config.xsd"> <!-- global config --> <webservice name="Calculator"> <interface name="org.pustefixframework.tutorial.calculator.CalculatorService"/> <implementation name="org.pustefixframework.tutorial.calculator.CalculatorServiceImpl"/> </webservice> </webservice-config>
Each of these webservice configurations requires at least the following options to be set:
Option | Description |
---|---|
name |
The name attribute specifies the name of the webservice. This name does not have to match
the name of the Java class. It will be used to identify the service, when the web service servlet is
requested by a client.
|
interface |
The <interface/> tag specifies the name of interface for the web service. Only methods
defined in this interface will be exposed as web service methods.
|
implementation |
The <implementation/> tag references the actual implementation of the service.
|
Also, most of the global options (see Table 4.1, “Global webservice configuration ”) can be used in the local web service configuration to override them for the specified service.
Accessing the context | |
---|---|
If your web service extends the <context name="pfixtutorial:ajax-calculator::servlet:calc"/> |
With these steps, you have finished all Java and configuration work packages and can continue implementing the client
side of your application. Open the main_Calculator.xml
file and add some HTML as a frontend for
your calculator application:
<?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"> <!-- Include external JavaScript --> <pfx:include part="javascript-includes"/> <h1>AJAX Calculator Tutorial</h1> <fieldset> <legend>Calculator</legend> A: <input type="text" id="a"/><br/> B: <input type="text" id="b"/><br/> <input type="button" value="add" onClick="add();"/> <input type="button" value="subtract" onClick="subtract();"/> <input type="button" value="multiply" onClick="multiply();"/> </fieldset> </theme> </part> </include_parts>
The HTML page contains several controls:
Two text input fields (with ids a
and b
), in which the user enters the two values, that are used for the calculation.
A button labeled add
which will call the JavaScript function add()
when clicked.
A button labeled subtract
which will call the JavaScript function subtract()
when clicked.
A button labeled multiply
which will call the JavaScript function multiply()
when clicked.
To implement an AJAX application, you will have to use JavaScript. This has been left out in the last example of your page,
there is only a <pfx:include/>
tag, which includes a second part javascript-includes
defined in the same page. This part is used to separate the HTML from all your JavaScript includes and make the page
more maintainable.
Pustefix does not only provide server side implementation helpers for your AJAX applications, but also some client side JavaScript
classes which hide all transportation details from you. Pustefix will also generate JavaScript stub classes for the Java services
you exposed via the WebServiceServlet
. To use the JSONWS
implementation, you have to
include the following files:
/core/script/httpRequest.js
provides an abstraction over the different XmlHttpRequest
implementations in the different browsers.
/core/script/webservice_json.js
provides an abstraction of the JSONWS
protocol used by Pustefix.
The JavaScript stub for your calculator service is not available as a file, but is generated on demand by the servlet.
To include this generated JavaScript code, Pustefix provides the <pfxwsscript/>
tag, which takes the name of the
service (as specified in the <webservice/>
tag in your web service configuration) in the name
attribute. This tag will generate the <script/>
tag that requests the generated JavaScript code from
the web service servlet.
The following listing shows the javascript-includes
part, which is referenced by the frontend of your application:
<?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="javascript-includes"> <theme name="default"> <script type="text/javascript" src="/core/script/httpRequest.js"></script> <script type="text/javascript" src="/core/script/webservice_json.js"></script> <pfx:wsscript name="CalculatorService"/> </theme> </part> </include_parts>
If you are taking a look at the generated JavaScript code, you will see, that this code defines a new class called
WS_CalculatorService
, which provides three methods:
add()
subtract()
divide()
These are exactly the same methods that the CalculatorService
interface declared in your Java code.
//Autogenerated webservice stub (don't modify this code manually!) function WS_CalculatorService(context) { pfx.ws.json.BaseStub.call(this,"CalculatorService",context); } WS_CalculatorService.prototype=new pfx.ws.json.BaseStub; WS_CalculatorService.prototype.subtract=function() { return this.callMethod("subtract",arguments,2); } WS_CalculatorService.prototype.multiply=function() { return this.callMethod("multiply",arguments,2); } WS_CalculatorService.prototype.add=function() { return this.callMethod("add",arguments,2); }
This class acts as a remote proxy for the Java business logic you implemented earlier. If you call any of these methods, the calculation will not be done on the client, but the request will be sent to the server, where it will be processed by the Java implementation. The response will then be sent back to the client and is available in your JavaScript application.
So all that is left to do, is implement the client side logic, that will create the proxy object and delegate the
calculation to it. This is done in a new part javascript-logic
, which you need to include
in the content
part of your application:
<?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"> <!-- Include external JavaScript --> <pfx:include part="javascript-includes"/> <pfx:include part="javascript-logic"/> <!-- HTML has been left out --> </theme> </part> </include_parts>
In this part, you are using the <pfx:script/>
tag, which is used to insert inline JavaScript
into your page. In this script, you create a new instance of WS_Calculator
and assign it to
a global JavaScript variable.
Furthermore, you implement the three missing functions add()
, subtract()
and
multiply()
, which are called, if the corresponding buttons are clicked. All three methods are implemented
the same way: The values of the two input fields are read and the corresponding method of the proxy object is called with
these values. The return value of the proxy method is displayed to the user, using the alert()
function.
<?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="javascript-logic"> <theme name="default"> <pfx:script> var calcProxy = new WS_Calculator(); function add() { var a = parseInt(document.getElementById('a').value); var b = parseInt(document.getElementById('b').value); alert(calcProxy.add(parseInt(a),parseInt(b))); } function subtract() { var a = parseInt(document.getElementById('a').value); var b = parseInt(document.getElementById('b').value); alert(calcProxy.subtract(parseInt(a),parseInt(b))); } function multiply() { var a = parseInt(document.getElementById('a').value); var b = parseInt(document.getElementById('b').value); alert(calcProxy.multiply(parseInt(a),parseInt(b))); } </pfx:script> </theme> </part> </include_parts>
Now you can open the page, enter any two integer numbers and click on any button. Your application will send the request to your business logic on the server and display the result on the client without reloading the page.
This tutorial showed you, how to implement an AJAX application based on Pustefix in several minutes. While this example only used a very simple business logic and transferred only primitive type, AJAX applications in Pustefix are not restricted to built-in Java types. Pustefix is able to send any Java-bean style objects from the client to the server and vice-versa. You only need to make sure, that there is an empty constructor in your beans.
Furthermore, the following features are also supported by Pustefix:
Access any property of the context (including context resources) in your services.
Switch from JSONWS to SOAP without changing any of your services.
Implement custom protocols or custom serializers to fit your special needs.
Of course, you can combine the AJAX features of Pustefix with any other feature in Pustefix with ease. For example, it is possible to force AJAX requests to be made secure or to restrict a service to users with sepcial roles attached.