Pustefix Framework Reference Documentation

Version 0.22.x

This documentation relates to the latest Pustefix release from the 0.22 line.

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.


Table of Contents

Introduction
1. What's new in Pustefix 0.22.x
1.1. What's new in Pustefix 0.22.0
1.2. What's new in Pustefix 0.22.1
1.3. What's new in Pustefix 0.22.2
1.4. What's new in Pustefix 0.22.6
1.5. What's new in Pustefix 0.22.7
2. Installation
2.1. Requirements
2.2. Generating a new project from a Maven archetype
2.3. Getting the source code
3. Architecture
3.1. High Level View
3.2. The Pustefix backend system
3.3. Recursive XSL Transformations
3.3.1. XSL Targets
4. Configuring Pustefix
4.1. Overview
4.2. Customization tools
4.3. Configuration files
4.3.1. XML property files syntax
4.3.2. Project descriptor (project.xml)
4.3.3. Page Configuration (depend.xml)
4.3.4. Sitemap configuration
4.3.5. ContextXMLService configuration file
4.3.6. DirectOutputService configuration file
4.3.7. WebServices
4.3.8. Configuration Fragments
4.4. Spring configuration and customization
4.4.1. Customizing the Spring configuration
4.4.2. Environment-dependent Spring property files
4.4.3. Environment-dependent Spring profiles
4.4.4. Reserved Spring bean names
4.5. Logging configuration
4.5.1. Logback
4.5.2. Log4j 2
4.5.3. Log4j (deprecated)
5. Core Pustefix tag library
5.1. Defining the structure of a document
5.1.1. Structure of a Type 1 document
5.1.2. Structure of a Type 2 document
5.2. Creating links to internal and external pages
5.2.1. pfx:button
5.2.2. pfx:url
5.2.3. pfx:elink
5.3. Including text and images
5.3.1. Include parts (<pfx:include>)
5.3.2. Generated include requests (<pfx:maincontent>)
5.3.3. Displaying images (<pfx:image>)
5.3.4. Dynamically including parts at rendering time
5.3.5. Checking include part existence (<pfx:checkinclude>)
5.3.6. Include parameters (<pfx:includeparam>)
5.4. Displaying messages
5.4.1. Message tags
5.4.2. MessageSource configuration
5.4.3. Message format
5.5. Handling HTML forms
5.5.1. Form creation
5.5.2. Submitting forms
5.5.3. Arguments, comands and anchors
5.5.4. Form elements
5.5.5. Handling error conditions
5.5.6. Avoiding duplicate form submission
5.5.7. Preventing CSRF attacks
5.6. Miscellaneous utility tags
5.6.1. Checking page status
5.6.2. Displaying content based on the language
5.6.3. Displaying content based on the theme
5.6.4. Using the Pustefix console
5.7. XSLT extensions
5.7.1. XPath functions
5.7.2. XSL extension elements
6. Important Concepts
6.1. HTTP request handling
6.1.1. HttpRequestHandlers provided by the core framework
6.2. Processing of Requests
6.2.1. The Context
6.2.2. PageFlow
6.2.3. States
6.2.4. Influencing the page request cycle
6.2.5. The basic Pustefix Request Cycle
6.2.6. Pustefix State implementations
6.2.7. Pustefix PageFlow implementation: DataDrivenPageFlow
6.3. The data model: Context resources
6.4. Wrappers and Handlers
6.4.1. IWrappers
6.4.2. The InputHandler interface
6.4.3. The deprecated IHandler interface
6.5. StatusCodes
6.6. ContextInterceptors
7. Advanced topics
7.1. Variants and Themes
7.1.1. Themes
7.1.2. Variants
7.2. Page alternatives
7.3. Internationalization
7.4. Multitenancy
7.5. Dynamic resource inclusion
7.5.1. Including resources from modules
7.5.2. Dynamic search
7.5.3. Overriding module resources
7.6. Authentication and authorization
7.6.1. Managing Roles
7.6.2. Custom conditions
7.6.3. Custom RoleProvider
7.7. AJAX services
7.7.1. Service configuration
7.7.2. Exception handling
7.7.3. Development tools
7.7.4. Callback mechanisms
7.7.5. Type mapping
7.8. Object-to-XML mapping
7.8.1. Serialization process
7.8.2. Built-in serializers
7.8.3. Custom serializers
7.8.4. Using JAXB serialization
7.9. Annotation-based IWrapper creation
7.9.1. IWrapper annotations
7.10. Scripted workflows
7.10.1. Parameters and Variables
7.10.2. Statements
7.11. Scripting Langauge support
7.11.1. IHandler
7.11.2. State
7.11.3. Implementation Details
7.12. The Pustefix EventBus
7.13. Request IDs for log correlation
7.14. The Pustefix Editor
8. Using Spring MVC
8.1. Pustefix states as Spring MVC controllers
8.2. AnnotationMethodHandlerAdapter customization (deprecated)
8.3. RequestMappingHandlerAdapter customization
8.4. Example: Pagination
9. Module Support
9.1. Resources within library JARs
9.2. Creating new modules using the Maven archetype
10. Testing
10.1. Unit testing
10.2. Integration testing
11. Tooling
11.1. Pustefix internals page
11.2. Special parameters for development
11.3. Maven plugins
11.3.1. Pustefix XSL Generate Plugin
11.3.2. Pustefix Pagelist Plugin
12. Upgrading to a newer Pustefix version
12.1. Upgrading from Pustefix 0.21 to 0.22
12.2. Upgrading from Pustefix 0.20 to 0.21
12.3. Upgrading from Pustefix 0.18 to 0.19
12.3.1. Library dependencies
12.4. Upgrading from Pustefix 0.17 to 0.18
12.4.1. depend.xml
12.5. Upgrading from Pustefix 0.16 to 0.17
12.6. Upgrading from Pustefix 0.15 to 0.16
12.6.1. web.xml
12.6.2. project.xml
12.6.3. depend.xml
12.6.4. Tag library
12.6.5. Modules
12.6.6. Session handling
12.6.7. AJAX webservices
13. What's new (coming from the preceding release line)
13.1. What's new in Pustefix 0.15.6
13.1.1. Module enhancements
13.2. What's new in Pustefix 0.15.7
13.2.1. Module enhancements
13.3. What's new in Pustefix 0.15.11
13.3.1. Configuration system enhancements
13.4. What's new in Pustefix 0.15.13
13.4.1. Module/configuration enhancements
13.4.2. Tooling enhancements
13.5. What's new in Pustefix 0.15.14
13.5.1. IWrapper enhancements
13.6. What's new in Pustefix 0.15.17
13.6.1. Module enhancements
13.7. What's new in Pustefix 0.16.0
13.8. What's new in Pustefix 0.16.5
13.9. What's new in Pustefix 0.17.0
13.10. What's new in Pustefix 0.18.0
13.10.1. Multi-tenancy
13.10.2. Multi-language
13.10.3. Sitemap and i18n pagenames
13.10.4. Page alternatives
13.10.5. Pustefix modules
13.10.6. Automatic configuration
13.11. What's new in Pustefix 0.18.2
13.11.1. Context information for XSLT errors
13.11.2. Switchable XSLT tooling extensions
13.11.3. Search-engine sitemap generation
13.11.4. Direct page alternative linking
13.11.5. OXM support for BigDecimal and BigInteger
13.12. What's new in Pustefix 0.18.5
13.12.1. CMIS support
13.12.2. Page aliases
13.13. What's new in Pustefix 0.18.6
13.13.1. Include part existence check
13.14. What's new in Pustefix 0.18.7
13.14.1. Custom sitemap attributes
13.15. What's new in Pustefix 0.18.9
13.15.1. Log directory configuration
13.16. What's new in Pustefix 0.18.13
13.16.1. Getting environment properties from within XSLT
13.17. What's new in Pustefix 0.18.14
13.17.1. External session invalidation synchronization support
13.18. What's new in Pustefix 0.18.27
13.18.1. Global output resources
13.18.2. Cookie-only session tracking
13.19. What's new in Pustefix 0.18.29
13.19.1. Contextual render includes
13.19.2. XSL parameters within include parameter XPath expressions
13.20. What's new in Pustefix 0.18.30
13.20.1. Object-to-XML mapping with JAXB
13.21. What's new in Pustefix 0.18.31
13.21.1. TargetGenerator tooling extensions toggle
13.22. What's new in Pustefix 0.18.34
13.22.1. Extended include parameter support
13.23. What's new in Pustefix 0.18.35
13.23.1. Early logging configuration
13.24. What's new in Pustefix 0.18.38
13.24.1. Full text search
13.24.2. Default page alternatives
13.25. What's new in Pustefix 0.18.39
13.25.1. Setting pfx:button page at runtime
13.26. What's new in Pustefix 0.18.42
13.26.1. Multitenancy/-language support for static resources
13.27. What's new in Pustefix 0.18.59
13.27.1. Rendering pages with static DOM tree during development
13.27.2. Added XSL extension elements for logging and debugging
13.28. What's new in Pustefix 0.18.63
13.28.1. Instance level XSLT extension functions
13.28.2. Preserving parameters on redirect
13.28.3. Reflective include information
13.29. What's new in Pustefix 0.18.64
13.29.1. Support standard Java regular expressions in IWrapper checks
13.29.2. Support multitenant property configuration
13.29.3. Configurable result DOM viewing
13.30. What's new in Pustefix 0.18.70
13.30.1. Page alias names with slashes
13.31. What's new in Pustefix 0.18.71
13.31.1. Environment-dependent Spring property files
13.32. What's new in Pustefix 0.18.87
13.32.1. New XPath string functions using pattern matching
13.33. What's new in Pustefix 0.19.0
13.34. What's new in Pustefix 0.19.4
13.35. What's new in Pustefix 0.19.8
13.36. What's new in Pustefix 0.19.9
13.37. What's new in Pustefix 0.19.11
13.38. What's new in Pustefix 0.19.14
13.39. What's new in Pustefix 0.19.15
13.40. What's new in Pustefix 0.19.17
13.41. What's new in Pustefix 0.19.21
13.42. What's new in Pustefix 0.19.23
13.43. What's new in Pustefix 0.19.24
13.44. What's new in Pustefix 0.19.25
13.45. What's new in Pustefix 0.19.27
13.46. What's new in Pustefix 0.19.28
13.47. What's new in Pustefix 0.19.29
13.48. What's new in Pustefix 0.19.30
13.49. What's new in Pustefix 0.20.0
13.50. What's new in Pustefix 0.20.1
13.51. What's new in Pustefix 0.20.14
13.52. What's new in Pustefix 0.20.19
13.53. What's new in Pustefix 0.20.20
13.54. What's new in Pustefix 0.20.25
13.55. What's new in Pustefix 0.20.26
13.56. What's new in Pustefix 0.20.27
13.57. What's new in Pustefix 0.20.28
13.58. What's new in Pustefix 0.20.29
13.59. What's new in Pustefix 0.20.31
13.60. What's new in Pustefix 0.21.0

List of Figures

3.1. High Level View of the system
3.2. The Pustefix backend system
3.3. Recursive XSL transformations
6.1. Pustefix HTTP request handlers
6.2. ContextInterceptors
11.1. Pustefix internals - Framework information
11.2. Pustefix internals - JVM information
11.3. Pustefix internals - Target generator information
11.4. Pustefix internals - Full text search

List of Tables

4.1. Attributes of the <make> tag
4.2. Attributes of the <target> tag
4.3. Attributes of the <target> tag
4.4. Reserved Spring bean names
5.1. The Core Pustefix XSLT Tags
5.2. Attributes of the pfx:button tag
5.3. Attributes of the pfx:include tag
5.4. Attributes of the pfx:maincontent tag
5.5. Attributes of the pfx:image tag
5.6. Attributes of the pfx:render tag
5.7. Attributes of the pfx:forminput tag
5.8. Attributes of form submit controls
5.9. Attributes of pfx:anchor
5.10. Attributes of pfx:argument
5.11. Attributes of pfx:command
5.12. Attributes of pfx:xinp[@type="text"]
5.13. Attributes of pfx:xinp[@type="radio|check"]
5.14. Attributes of pfx:xinp[@type="select"]
5.15. Attributes of pfx:option
5.16. Attributes of the pfx:checkactive and pfx:checknotactive tags
6.1. Variables of the context during processing
6.2. Attributes of an iwrp parameter
11.1. Special parameters for development

List of Examples

5.1. Using <pfx:checkfield>
7.1. Configuring roles

Introduction

The Pustefix Framework is an open-source Java web application framework for developing request-based MVC-style web applications using Spring, XML and XSLT.

A key feature of the framework is the clear separation of view and business logic, reflected by having two loosely coupled main parts:

  • A machinery to apply recursive XSLT transformations that produces the UI of the web application.
  • A Java framework that takes input from the UI to change the application data and supplies changes of the application data back to the UI.

Together, the framework acts similar to the Model-View-Controller pattern (as far as this is possible in the context of a web application).

This reference guide covers all important topics that you need to know when working with any aspects of the Pustefix framework. If you are new to Pustefix we recommend reading one of the tutorials.

Chapter 1. What's new in Pustefix 0.22.x

This section lists the new features and enhancements of the current Pustefix release line. If you're interested in new features which already became available with the prior Pustefix release line, you can take a look at Chapter 13, What's new (coming from the preceding release line).

If you're migrating an existing appliation to the new Pustefix version, you should have a look at Chapter 12, Upgrading to a newer Pustefix version.

1.1. What's new in Pustefix 0.22.0

Added support for Spring 5. The minimum requirement now is Spring version 4.3.

Pustefix states used as Spring MVC controllers with RequestMappings now work either with the deprecated Spring WebArgumentResolvers, or with HandlerMethodArgumentResolvers. Using Spring 4.3 both are supported by Pustefix, since Spring 5 HandlerMethodArgumentResolvers are required (see Section 8.2, “AnnotationMethodHandlerAdapter customization (deprecated)” and Section 8.3, “RequestMappingHandlerAdapter customization”).

1.2. What's new in Pustefix 0.22.1

Added support for so-called protected submits, i.e. only the HTTP request method POST is allowed and a synchronizer token is used to prevent CSRF attacks. Protected submits are enabled by setting the protected attribute at the <input> element to true (see Section 4.3.5, “ContextXMLService configuration file” and Section 5.5.7, “Preventing CSRF attacks”).

1.3. What's new in Pustefix 0.22.2

Pustefix now can be used together with the standard Spring ApplicationContext implementations, i.e. without using the custom PustefixWebApplicationContext implementation. Therefore Pustefix provides a BeanDefinitionRegistryPostProcessor implementation, which does all the necessary setup (e.g. reading the Pustefix configuration and creating bean definitions). Thus you can configure the Spring part of your application in a standard way, e.g. using an AnnotationConfigWebApplicationContext and Spring Java Config (see Section 4.4, “Spring configuration and customization” or the Pustefix pustefix-mvctest sample application).

1.4. What's new in Pustefix 0.22.6

Added support for MessageSource preprocessing, i.e. now you can do regular expression based search and replace on MessageSource messages, e.g. you could introduce global placeholders processed at runtime, which avoids needing multiple versions of a file (see Section 5.4.2, “MessageSource configuration”).

1.5. What's new in Pustefix 0.22.7

Pustefix now supports running under Java 11 without doing any illegal access operations, i.e. will work with having the JVM option --illegal-access=deny enabled too.

Chapter 2. Installation

2.1. Requirements

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

  • JDK 8 or higher
  • POSIX-like operating system (Pustefix has been tested with Linux and Mac OS X, but might also work with other systems like *BSD)
  • Apache Maven 3 or higher

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

2.2. Generating a new project from a Maven archetype

Pustefix provides some Maven archetypes for quickly creating new applications. A good starting point is the basic application archetype. Just call mvn archetype:generate and select the archetype org.pustefixframework:pustefix-archetype-basic:

$ mvn archetype:generate -Dfilter=pustefix

After choosing the pustefix-archetype-basic Maven will ask you for your project's groupId, artifactId, version and package. Having finished these settings Maven will generate the new project within a new directory called like the artifactId and located within the current working directory (default is myapp).

$ cd myapp
$ mvn tomcat7:run-war

The generated project is a standard Maven project with war packaging type, i.e. you can immediately build and run your application, e.g. using Tomcat by calling mvn tomcat7:run-war and opening http://localhost:8080 in your browser.

2.3. Getting the source code

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

$ git clone https://github.com/pustefix-projects/pustefix-framework.git pustefix
$ cd pustefix
$ git checkout -b tutorial `git tag -l "pustefixframework-*" --sort v:refname | tail -1`
$ cd pustefix-tutorial/first-app
$ mvn tomcat7:run-war

This will clone the Pustefix Git repository, checkout/branch the latest Pustefix release tag, and run the tutorial application.

Chapter 3. Architecture

3.1. High Level View

Figure 3.1, “High Level View of the system” shows the two main parts of the Pustefix system. On the left you can see the java framework. A request coming from the browser enters the business logic. After the processing has finished, the business logic delivers the result as a (in memory) DOM tree. To get a more detailed overview of the business logic, take a look at Chapter 6, Important Concepts.

The stylesheet that's responsible to render the UI to displays the result data is requested from the XML/XSLT generator. It uses the DOM tree as input to create the HTML output that is displayed on the browser.

Figure 3.1. High Level View of the system

High Level View of the system

The stylesheet generator makes heavy use of caching to ensure that transformations are never made twice unless the result is out of date. Normally all generated stylesheets are cached in memory (and on disc). If you don't have enough memory to hold your site in RAM, you can specify other cache objects. E.g. we supply a LRU cache that can be configured to hold only the last N generated objects in memory.

3.2. The Pustefix backend system

Figure 3.2, “The Pustefix backend system” shows, how the different interfaces and classes in Pustefix are connected (not including web services and direct output support).

Figure 3.2. The Pustefix backend system

The Pustefix backend system

The Pustefix application runs within a Spring ApplicationContext that is created by the DispatcherServlet. The servlet dispatches all requests to HttpRequestHandlers that are managed as beans in the application context. PustefixContextXMLHttpRequestHandler handles the requests to Pustefix pages and takes care of session, cookie and SSL management. The actual request processing (workflow handling, dispatching to the right State) is performed by Context (or more precisely ContextImpl).


3.3. Recursive XSL Transformations

The XML/XSLT System of Pustefix is responsible for generating the final stylesheet that represents the static content of a page. This stylesheet is then used together with the DOM tree that holds the result of the request (as given by the business logic) to produce the final HTML output.

Figure 3.3, “Recursive XSL transformations” shows the typical transformations and files that are involved in producing the final stylesheet BazPage.xsl.

Note that we only discuss the common case here, arbitrary complex and deep transformation trees are in fact possible.

Figure 3.3. Recursive XSL transformations

Recursive XSL transformations

The red boxes are supplied by the framework, you don't need to create them yourself and as an application programmer, you can't change them. Currently this is only the case for core/xsl/master.xsl, core/xsl/metatags.xsl, core/xsl/customizemaster.xsl and other stylesheets that make up the core environment (these are not shown as they are included into master.xsl and metatags.xsl via xsl:include transparently for the user).

The green boxes are the result of XSL transformations.

The blue boxes represent files that you need to create yourself. The [PROJECT]/xsl/skin.xsl and [PROJECT]/xsl/metatags.xsl files are special, as they are not a target (see below) but just another XSLT stylesheet that can be included via xsl:include into master.xsl and metatags.xsl resp. [PROJECT]/xsl/skin.xsl contains the project specific templates that should apply on the last transformation stage, while [PROJECT]/xsl/metatags.xsl contains the project specific templates that apply only on the first stage.

There are projects that don't use a [PROJECT]/xsl/skin.xsl stylesheet at all or include even more stylesheets. Making master.xsl aware of the presence of the [PROJECT]/xsl/skin.xsl stylesheet is part of the transformation from core/xsl/master.xsl + core/xsl/customizemaster.xsl --> master.xsl

It'a also posible that a project doesn't use a [PROJECT]/xsl/metatags.xsl stylesheet or includes more stylesheets: Similar to master.xsl it's the responsibility of the transformation from core/xsl/metatags.xsl + core/xsl/customizemaster.xsl --> metatags.xsl to customize the resulting metatags.xsl to include the stylesheets.

The [PROJECT]/xml/FooBase.xml file defines the structure of the "BazPage" page (e.g. frames, the outer table structure if you do the layout with tables or divs and the like). You define one of these structural xml files for every layout you want to use in your project (the number of structural xml files is typically quite small, as many pages share the same layout).

The blue discs blue discs represent include parts. These are little snippets of XML code that make up the actual content of the page. As can be seen from the diagram, they can include each other, as long as there is no cyclic inclusion (so no include part can include itself either directly or indirectly). Include parts have a name and are organized into so called include documents. These can hold an arbitrary number of parts.

3.3.1. XSL Targets

A target is everything that is the result of a XSLT transformation as seen in Figure 3.3, “Recursive XSL transformations”. It is also obvious that a target can be used to create new targets. For the sake of completeness, the initial XML or XSL files that are used in transformations are called targets, too.

The Pustefix system knows different types of targets:

  • Leaf targets are targets that are not the result of a XSL transformation, but are read directly from files. You only edit leaf targets, never virtual targets. The distinction between XML/XSL is made depending on the purpose the target serves. An XML target is read into the system without doing any special processing, while an XSL target is compiled into a templates object that is able to transform XML input.

    Examples for leaf targets in Figure 3.3, “Recursive XSL transformations” are FooBase.xml, core/xsl/metatags.xsl and core/xsl/master.xsl.

  • Virtual targets are the result of a XSL transformation. They don't exist as files (in fact they do, but only to cache the result on the harddisk. These cache files must never be edited by hand). The difference between the XML/XSL type is the same as with the leaf targets.

    Examples for leaf targets in Figure 3.3, “Recursive XSL transformations” are BazPage.xml, and BazPage.xsl.

Chapter 4. Configuring Pustefix

4.1. Overview

Developing a new Pustefix application requires (besides developing the business logic and the UI) that you edit a bunch of configuration files. In general those file are located under src/main/webapp/WEB-INF.

4.2. Customization tools

Virtually all configuration files used by Pustefix support a mechanism called "customization". You may use this customization support to use different portions of a configuration file depending on the environment within your application is running. For this task, the customization tools provide a choose tag which is similar to the choose tag provided by XSLT.


<choose>
  <when test="XPathExpression">
    <!-- Configuration code -->
  </when>
  <when test="XPathExpression">
    <!-- Configuration code -->
  </when>
  <otherwise>
    <!-- Configuration code -->
  </otherwise>
</choose>

    

At least one when tag has to be specified. Further when tags and the otherwise are optional. However, if specified, the otherwise tag has to be the last one.

The XPath expressions may contain references to the variables

  • mode: The execution environment mode, e.g. test, stage or prod.

  • uid: The name of the OS user running the application.

  • fqdn: The fully qualified domain name of the machine where the application is running.

  • machine: The host name of the machine where the application is running.

  • You not only have access to the predefined variables. You can also reference System properties and ServletContext init parameters. If you want to override the values of an automatically set predefined property, you can do this by adding an according context init parameter to your web.xml.

It is an error to reference a variable that is not defined. Therefore you might use the special XPath function pfx:isSet('variableName') to check if a variable with a certain name is defined.

4.3. Configuration files

For each Pustefix application you need to create a definition file that contains all the information about your application (including references to other application-specific configuration files). This file has to be named project.xml and must be placed in the WEB-INF directory. It can be accompanied by a Spring bean definition file that must be called spring.xml. This file may contain arbitrary definitions for beans that will be created within the Spring ApplicationContext automatically created for the web application.

All other configuration files can theoretically have arbitrary names, however we strongly recommend using the naming convention used in this reference documentation.

4.3.1. XML property files syntax

Some parts of the Pustefix framework are configured using Java properties. To ease this configuration Pustefix provides you with a special XML format which is read instead of the usual Java property file format. This format provides some customization mechanism to allow configuration options to depend on settings like the makemode or the machine the application is being built on.

The structure of a standard .xml property file is very easy:

  
  <properties
    xmlns="http://www.pustefix-framework.org/2008/namespace/properties-config"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/properties-config
                        http://www.pustefix-framework.org/2008/namespace/properties-config.xsd">
    
    <prop name="a.property.name">a.property.value</prop>
    <!-- if you're using multitenancy, you can set or override properties depending on the tenant
    <prop name="a.property.name" tenant="a.tenant">a.property.value</prop> -->
  </properties>
  
        

The prop tag is the most primitive way to enter a single property. The example above would simply result in the java property a.property.name=a.property.value. Pustefix allows to customize the creation of the property files using the mechanism described in Section 4.2, “Customization tools”.

You may reference customization variables in property values using the syntax ${variableName}. For example ${fqdn} will be replaced by the fully qualified domain name of the machine.

4.3.2. Project descriptor (project.xml)

The project.xml file contains references to all services and resources used by this project.


<project-config xmlns="http://www.pustefix-framework.org/2008/namespace/project-config">
  
  <project>
    <!-- Short project name, should equal the name 
         of the project directory                  --> 
    <name>projectname</name>
    <!-- Description shown in Pustefix CMS -->
    <description>Description for this project</description>
    <!-- 
      add
    <enabled>false</enabled>
      to make disregard this project when building the
      server configuration 
    -->
    <!-- you can optionally specify which languages should be switchable at runtime -->
    <!--
    <lang default="true">en_UK</lang>
    <lang>de_DE</lang>
    -->
  </project>
  
  <!-- You can optionally configure tenants bound to specific hosts supporting different languages.-->
  <!--
  <tenant name="CA_market">
    <choose>
      <when test="$mode='prod'">
        <host>.*\.ca$</host>
      </when>
      <otherwise>
        <host>^ca\..*</host>
      </otherwise>
    </choose>
    <lang default="true">en_CA</lang>
    <lang>fr_CA</lang>
  </tenant>

  <tenant name="US_market">
    ...
  </tenant>
  -->

  <editor>
    <!-- Set this to false to make the project disappear in the Pustefix CMS 
         (can be overridden using the ServletContext init parameter 'editor.enabled') -->
    <enabled>true</enabled>
    <!-- Location of the Pustefix CMS, does not need to be changed usually 
         (can be overridden using the ServletContext init parameter 'editor.location') -->
    <location>http://cms.${fqdn}/</location>
    <!-- Authentication secret for editor communication
         (can be overridden using the ServletContext init parameter 'editor.secret') -->
    <secret>somevalue</secret>
  </editor>
  
  <xml-generator>
    <!-- Path to the configuration file of 
         the TargetGenerator for this project -->
    <config-file>docroot:/WEB-INF/depend.xml</config-file>
    <choose>
      <when test="$mode='prod'">
        <!-- Set this to false if you want to skip the check for changed stylesheets on every request.
             By default Pustefix will all dependant files for changes. -->
        <check-modtime>false</check-modtime>
        <!-- Set capacity and implementation class of the in-memory caches for includes and targets.
             Defaults (if you leave out the elements):
             <include-cache capacity="30" class="de.schlund.pfixxml.targets.LRUCache"/>
        -->
        <include-cache capacity="50"/>
        <target-cache capacity="50"/>
        <render-cache capacity="50"/>
      </when>
      <when test="$mode='prelive'">
        <!-- Enable/disable development tooling extensions (dynamic include info, console, etc.) 
             By default tooling is enabled if not in production mode. So this option is basically
             meant for non-production modes where you nevertheless want to disable tooling. 
        -->
        <tooling-extensions>false</tooling-extensions>
      </when>
    </choose>
  </xml-generator>
  
  <!-- automatically generate searchengine sitemap file sitemaps.xml 
       (according to sitemaps protocol from http://www.sitemaps.org), 
       set the attribute "type" to "mobile" to generate a mobile sitemap-->
  <searchengine-sitemap/>

  <!-- DEPRECATED - Configure tracking-mode in web.xml instead!
       Sets the session tracking strategy. Default is 'COOKIE'.
       Alternatively you can set 'URL' to force session tracking
       within the URL, even if clients support cookies. You can completely
       disable URL based session tracking by setting 'COOKIEONLY'. You can
       completely leave out this element, if you go with the default. -->
  <!-- <session-tracking-strategy>COOKIE</session-tracking-strategy> -->

  <!-- Set an initial session timeout 'value' (in seconds) used until 
       more than 'requestlimit' requests are made within a session.
       This element is optional. By default no separate timeout
       for the initial requests is used. -->
  <initial-session-timeout value="600" requestlimit="3"/>

  <application>
    
    <!-- Path that static resources will be delivered from (set i18n attribute to "true" for multitenancy/-language support)-->
    <docroot-path>docroot:/htdocs</docroot-path>
    
    <!-- URI requests to / are redirected to (optional)-->
    <default-path>/somepage</default-path>
    
    <!-- Only one context-xml-service may be specified per project -->
    <context-xml-service>
      <!-- Path to the configuration file for the service -->
      <config-file>docroot:/WEB-INF/config.conf.xml</config-file>
      <!-- Set this to true if you want to skip the last transformation and only get a
           xml document containing all needed information for an external renderer: -->
      <!-- <render-external>true</render-external> -->
      <!-- Set the maximum number of DOMs stored for reusage (default 5): -->
      <!-- <max-stored-doms>5</max-stored-doms> -->
      <!-- Set the timeout (in seconds) for removing old DOMs (default 300): -->
      <choose>
        <when test="$mode='prod'">
          <session-cleaner-timeout>20</session-cleaner-timeout>
        </when>
        <otherwise>
          <session-cleaner-timeout>600</session-cleaner-timeout>
          <!-- Allow viewing of the DOM tree model, which is enabled 
               by default in all non-production modes -->
          <!--<show-dom>true</show-dom>-->
          <!-- Maximum number of DOM trees to be displayed in DOM tree view -->
          <!-- <dom-history-size>5</dom-history-size> -->
        </otherwise>
      </choose>
    </context-xml-service>
    
    <direct-output-service>
      <!-- Path to the configuration file for the service -->
      <config-file>docroot:/WEB-INF/direct.conf.xml</config-file>
    </direct-output-service>

    <!-- Extra paths for static resources (set i18n attribute to "true" for multitenancy/-language support)-->
    <static expires="3600">
      <!-- The expires attribute and elements set the "Cache-Control: max-age" header value for static resources.
           The expires value specifies the calculation base and the number of seconds. Default base is access 
           time (default prefix "A" can be omitted). Optionally you can use the modification time by prefixing
           the value with "M" (e.g. "M3600", see Apache mod_expires for more detailed information).
      -->
      <!--
      <expires type="image/png">A3600</expires>
      <expires type="text/css">M3600</expires>
      -->
      <path>img</path>
      <path i18n="true">errorpages</path>
    </static>
   
    <!-- Exceptions thrown by the business logic or Pustefix itself can be handled on the top layer by so-called
         ExceptionProcessors, e.g. an ExceptionProcessor implementation can forward certain types of exceptions to
         error pages, log them to a file or pass them to an external tool. If you don't configure an ExceptionProcessor
         or the ExceptionProcessor doesn't create an HTTP response by itself, the default exception handling of the 
         servlet container applies. Pustefix provides a default implementation for development, which renders the
         exception together with some contextual information as HTML. But this processor should only be used in test
         mode, because it would expose system internals to the world. In production mode you should leave it out or
         register an alternative implementation.
    -->
    <choose>
      <when test="$mode='test'">
        <exception-processing>
          <process type="java.lang.Throwable" 
                   processor="de.schlund.pfixxml.exceptionprocessor.UniversalExceptionProcessor"/>
          <!-- <process type="mypackage.MyException" forward="true" page="/error.html"/> -->
        </exception-processing>
      </when>
    </choose>

    <!--  Custom Deplyment descriptor code
    <web-xml>
      <jee:web-app xmlns:jee="http://java.sun.com/xml/ns/javaee">
        <choose>
          <when test="$mode='test'">
            <jee:display-name>foo</jee:display-name>
          </when>
        </choose>
      </jee:web-app>
    </web-xml>
    -->
    
  </application>
  
  <!-- Example configuration of Spring MessageSources -->
  <messagesources>
    <messagesource type="po">
      <basename>module://my-module/i18n/messages</basename>
      <basename>module://my-module/i18n/errors</basename>
    </messagesource>
    <messagesource type="properties" basename="/WEB-INF/messages"/>
  </messagesources>

</project-config>


      

As you can see, the configuration file consists of different sections: One for information about the project, one for configuring the Pustefix CMS, one for the server configuration and one for the application itself. The application consists of services and static resources. Please note that only one context-xml-service is allowed per project.

4.3.3. Page Configuration (depend.xml)

The depend.xml is used to define the internal structure of the pages by defining, for every single page, the tree of transformations that need to be applied to certain files to get the final stylesheet (which is the representation of the page in Pustefix). For an overview over the transformation aspect of the whole framework, please go here.

To make life a little easier, you can use convenience tags that are automatically transformed by the runtime system when the file is loaded.

Structure of the depend.xml

The structure of the config file is show below:

<make project="MyProject" lang="en" themes="ThemeA ThemeB ... default">

  <!--
    The global section allows to set default values for ALL pages defined via the
    standardpage tag (see below). It's possible to set default params, and runtime
    stylesheets (see here). It's also possible to add more runtime stylesheets or
    overwrite params in the standardpage tag for a single page.
  -->
  <global>
    <include stylesheet="path/to/AStyleSheet"/>
    <!-- Stylesheets from module jars can be directly included by setting the module attribute. -->
    <!-- <include stylesheet="path/to/AStyleSheetFromModule" module="modulename"/> -->
    <param name="AName" value="AValue"/>
    <!-- Enable compressing of inline Javascript (requires YUI compressor or Google Closure Compiler in classpath) -->
    <cus:choose>
      <cus:when test="$mode = 'prod'">
        <param name="compress-inline-javascript" value="true"/>
      </cus:when>
    </cus:choose>
    <!-- Show missing includes, i.e. display a warning image if an include doesn't exist or has no matching theme. -->
    <cus:choose>
      <cus:when test="$mode = 'prod'">
        <param name="show-missing-include" value="false"/>
      </cus:when>
      <cus:otherwise>
        <param name="show-missing-include" value="true"/>
      </cus:otherwise>
    </cus:choose>
  </global>

  <config-include file="conf/myfile.xml" section="targets" module="mymodule"/>

  <!--
    The other allowed tags are target, global, standardmaster, standardmetatags and 
    standardpage. The latter three are only convenience tags that can be expressed 
    fully in terms of target tags (Expanding those tags is one of the duties of the 
    runtime transformation of the depend.xml file mentioned above).
  -->
  <target name="a_target_name.xsl" type="[xsl|xml]">...</target>
  <target name="another_target_name.xml" type="[xsl|xml]">...</target>...
  <standardmaster name="..."/>
  <standardmetatags name="..."/>
  <standardpage name="a_name" master="..."
                metatags="..." themes="..." variant="..."
                xml="a_base_xml_file.xml">
  <auto-standardpage lookup-path="txt/pages" lookup-module="..." 
                     master="..." metatags="..." themes="..." variant="..."
                     xml="a_base_xml_file.xml">
    ...
  </standardpage>
</make>

The <make/> tag

The <make> tag is the root element of the depend.xml

Table 4.1. Attributes of the <make> tag

Attribute Mandatory? Description
project mandatory The name of the project. This is the same as the corresponding entry in the project.xml file.
lang mandatory The default language of the project. This is the same as the value of the lang node's name attribute used in include parts.
themes optional

The attribute is a space separated list of theme names. It acts as a fallback queue of product branch names that should be checked in include parts to decide which branch to use. The least specific theme is always the "default" theme and therefore "default" should be the last theme in the list. The last theme in the list is used when a non-existing include part is created in the Pustefix CMS, so you can omit the "default" theme from the end of the list if you want to use another theme for newly created include parts. However the "default" theme will still be used as a fallback for existing include parts when no other matching theme variant of the include part exists. You should have at least a product branch named "default" in every include part to make sure to always have a valid fallback.

If it's not given, it defaults (in our example where the project name is "MyProject") to "MyProject default".

Note that this attribute only defines a global value, each target can define it's own themes list (see below for targets and their attributes).

The allowed characters for themes are: a-zA-Z0-9_+-


Target definition

The <target> tag is used to specify the XSL transformations in Pustefix. In most cases, you will not have to use the rather complex <target>, but use the convinience tags described in the section called “Standard page definition”, the section called “Standard master target definition” and the section called “Standard metatags target definition”.

Section 3.3.1, “XSL Targets” provides more information on the concept of targets in Pustefix.

<target name="baz.xsl" type="xsl" page="foo" variant="bar" themes="Theme_A Theme_B ... default">
  <!--
    depxml and depxsl reference other targets by their name attribute
    that serve as the XML resp. XSL input used to create this target
    via a XSL transformation. If for a given name attribute of either
    depxml or depxsl no other target definition is found, the transformation
    parent is supposed to be a leaf target and the name attribute is 
    interpreted as a path relative to the docroot.
  -->
  <depxml name="foo/xsl/bar.xsl"/>
  <depxsl name="foo/xsl/baz.xsl"/>

  <!--
    Additional dependencies
  -->
  <depaux name="foo/xsl/snarf.xsl"/>
  <depaux name="foo/xsl/fubar.xsl"/>

  <!--
    param tags supply XSL transformation parameters that are used when the target is generated.
  -->
  <param name="AName" value="AValue"/>
</target>

The <target> tag supports the following attributes:

Table 4.2. Attributes of the <target> tag

Attribute Mandatory? Description
name mandatory The name of the target. This name must be unique for the whole project (not the whole environment!)
type mandatory Must be xsl for a target that should be "compiled" into a templates object. Must be xml for every target that is used as input for a transformation.
themes optional This is just a local overwrite to the global themes attribute as explained on this page
page optional It must be set for any top level target (that means a target that is not itself used to generate other targets) that should be accessible via the page name. Note: having a non-toplevel target with a page attribute is considered an error.
variant optional It makes only sense when also a page attribute is set. This attribute will discriminate between targets that should be associated with the same page, but represent a differnt variant of this page.

Adding dependencies

<depaux> tags create user defined dependencies on the files they reference in their name attribute. Whenever the target generation system is asked for a target, all its dependencies are checked whether their modification time is older than the creation time of the target. Dependencies include by design the depxml and depxsl targets (which may be files in the case of a leaf target or another virtual target that returns its own creation time as the modification time) and all include files from which include parts are taken during the transformation of the target.

If any of these files or targets has been changed after the target was built, it is taken care of that the target is rebuild.

<depaux> just adds more dependencies "by hand" that are not automatically detected. In the example above, the file referenced is a XSLT stylesheets that's included via xsl:include into the foo.xsl stylesheet. Such external dependencies are not currently recognized automatically.

E.g. if you use a foo/xsl/fubar.xsl stylesheet that serves as a library of templates you want to include into foo.xsl, you need to add the following line to the target definition of foo.xsl to make the system recognize changes to foo/xsl/fubar.xsl.

<depaux name="foo/xsl/fubar.xsl"/>

Standard page definition

The standardpage tag is a convenience tag that encapsulates the typical definition of a complete page in the Pustefix system.

<standardpage name="BazPage" master="AName" metatags="AName" xml="xml/FooBase.xml"
              themes="Theme_A Theme_B ... default" variant="foo">
  <!--
    Note: All the child nodes are optional (and in fact usually not needed)
  -->
  <include stylesheet="xsl/runtime.xsl"/>
  <!-- Stylesheets from module jars can be directly included by setting the module attribute. -->
  <!-- <include stylesheet="xsl/runtimeFromModule.xsl" module="modulename"/> -->
  <param name="fubar" value="bar"/>
</standardpage>

The <standardpage> tag supports the following attributes:

Table 4.3. Attributes of the <target> tag

Attribute Mandatory? Description
name mandatory The name of the page. This must be a name already defined in a page tag in the navigation tree.
xml mandatory The name of a xml target to use as input for the "metatags transformation". Often this is a leaf target and one of the projects structural xml files.
module optional The name of the module from which the xml file should be loaded.
themes optional This attribute is a local overwrite of the global themes attribute explained here.
variant optional This attribute allows you to define variants of the same page. It's only possible to define variants of pages when there's already a "root" page, in other words a standardpage definition without the variant attribute. Variants also influence the local themes (in fact the visible aspect of variants is implemented in terms of themes).
master optional Default is to use the default definition of the master stylesheet (standardmaster without a name attribute).
metatags optional Default is to use the default definition of the metatags stylesheet (standardmetatags without a name attribute).

[Note]Page auto-configuration
If you're configuration contains a lot of repeating standardpage definitions only differing in the page name, you can alternatively use <auto-standardpage>, which will automatically create standardpage definitions for all found pages. Pages are searched looking for XML files in the directory specified by the lookup-path attribute (and lookup-module if the pages are located in modules). The page name will be derived from the XML file name. Apart from the name attribute all standardpage attributes can be used.
<auto-standardpage lookup-path="txt/pages" lookup-module="*" master="AName" metatags="AName">
  ...
</auto-standardpage>

After performing the transformation of the depend.xml on loading (automatically done by the runtime system system) this becomes

<target name="BazPage::foo.xsl" type="xsl" themes="foo Target_A Target_B ... default" page="BazPage" variant="foo">
  <!--
    For every target that is only used in the generation of one single page
    (if you look at the example given here, this is true for the generated
    targets BazPage.xml and BazPage.xsl) you must give a parameter called page
    with the name of the resulting page as the value for the standard XSLT tags
    to be able to work correctly. They need this information e.g. to create links
    to other pages and many other things. While the standardpage tag does this
    automatically for you make sure that you don't forget it for target structures
    you define yourself.

    If the master attribute is not given, the depxsl will be master.xsl
  -->
  <depxml name="BazPage::foo.xml"/>
  <depxsl name="master-AName.xsl"/>
  <param name="page" value="BazPage"/>

  <!--
    parameters given to the standardpage tag are supplied to the first of the two
    transformations. The outputencoding parameter is inserted by the build system.
    Refrain from supplying this parameter on your own, unless you really know what
    you do. Changing the encoding should be done in the project.xml file.
  -->
  <param name="fubar" value="bar"/>
  <param name="outputencoding" value="UTF-8"/>

  <!--
    All include tags given are runtime stylesheets, given to the transformation via
    the parameter stylesheets_to_include (as a space separated list if multiple
    include tags are given). Note that the needed depaux nodes are inserted automatically.
  -->
  <param name="stylesheets_to_include" value="xsl/runtime.xsl"/>
</target>

<target name="BazPage::foo.xml" type="xml" themes="foo Themes_A Themes_B ... default">

  <!--
    If the metatags attribute has not been given, the depxsl value is metatags.xsl  
  -->  
  <depxml name="xml/FooBase.xml"/>
  <depxsl name="metatags-AName.xsl"/>

  <param name="fubar" value="bar"/>
  <param name="page" value="BazPage"/>
</target>

Standard master target definition

The standardmaster tag is a convenience tag that encapsulates the typical target definition of the master.xsl stylesheet.

<standardmaster name="AName">
  <!-- The name attribute is optional -->
  <include stylesheet="xsl/skin.xsl"/>
  <!-- Stylesheets from module jars can be directly included by setting the module attribute. -->
  <!-- <include stylesheet="xsl/skinFromModule.xsl" module="modulename"/> -->
  <param name="AName" value="AValue"/>
</standardmaster>

After performing the transformation of depend.xml when the runtime system loads the file this becomes:

<target name="master-AName.xsl" type="xsl">
  <!--
    If the name attribute of the standardmaster tag has not been given,
    the value for the target's name attribute will be master.xsl.
  -->
  <depxml name="xsl/master.xsl" module="pustefix-core"/>
  <depxsl name="xsl/customizemaster.xsl" module="pustefix-core"/>
  <param name="AName" value="AValue"/>
  <param name="product" value="MyProject"/>
  <param name="lang" value="en"/>
</target>

Standard metatags target definition

The standardmetatags tag is a convenience tag that encapsulates the typical target definition of the metatags.xsl stylesheet.

<standardmetatags name="AName">
  <!-- The name attribute is optional -->
  <include stylesheet="xsl/metatags.xsl"/>
  <!-- Stylesheets from module jars can be directly included by setting the module attribute. -->
  <!-- <include stylesheet="xsl/metatagsFromModule.xsl" module="modulename"/> -->
  <param name="AName" value="AValue"/>
</standardmetatags>

After performing the transformation of depend.xml when the runtime system loads the file this becomes:

<target name="metatags-AName.xsl" type="xsl">
  <!--
    If the name attribute of the standardmetatags tag has not been given,
    the value of the name attribute here becomes metatags.xsl.
  -->

  <depxml name="xsl/metatags.xsl" module="pustefix-core"/>
  <depxsl name="xsl/customizemaster.xsl" module="pustefix-core"/>
  <param name="stylesheets_to_include" value="xsl/metatags.xsl "/>
  <param name="AName" value="AValue"/>
  <param name="product" value="MyProject"/>
  <param name="lang" value="en"/>
</target>

4.3.4. Sitemap configuration

The optional sitemap configuration file sitemap.xml can be used to define a logical page structure, e.g. for grouping pages according to a navigation or menu structure. This sitemap can be accessed during the XSL transformations using the XSL parameter $sitemap.

The sitemap can be also used to provide page name aliases. Pustefix differentiates between logcial page names and display page names. Within the sitemap you can set a display page name by setting a so-called page alias. Thus you can rename a page without having to change existing page references, i.e. internally the logical name is still used, but when Pustefix renders the page, page names in links, etc., are replaced by the display names.

In addition the sitemap allows the definition of page alternatives. Page alternatives can be used to create different representations of a page. They base on the same logical page, but can produce different content and display names, e.g. for landing pages.

Finally you can define so-called page groups, which let you group pages and add a common path prefix to their URL. Page groups can be nested arbitrarily, which will create compound path prefixes.

<sitemap>
  <!-- page elements can be arbitrarily listed/nested, the name attribute relates to the logical page name -->
  <page name="Home">
    <page name="..."/>
    <page name="..."/>
  </page>
  <!-- the 'alias' attribute can be used to provide a page alias which should be displayed instead of the logical name,
       you can optionally add the 'internal' attribute to exclude a page from the generated search engine sitemap or
       add a 'lastmod' attribute to generate an according lastmod element.
       You're also allowed to use arbitrary custom attributes -->
  <page name="Overview" alias="Summary" internal="true" lastmod="2017-06-27T18:29:39+01:00" foo="bar"/>
  <!-- using the alt element you can add page alternatives, matching is done using the mandatory key attribute -->
  <page name="Info">
    <alt key="cities" name="Cities"/>
    <alt key="nationalparks" name="NationalParks"/>
    <alt key="mountains" name="Mountains"/>
    <!-- Optionally you can set one page alternative to be the default one used when no page alternative is
         explicitly requested -->
    <!-- <alt key="mountains" name="Mountains" default="true"/> -->
  </page>
  <!-- the page-group name will appear as path prefix in the URL, setting the default attribute on a page will make it
       accessible under the group name, e.g. /mainpath/mainpage, /mainpath/subpath/ and /mainpath/subpath/subpage2 --> 
  <page-group key="main" name="mainpath">
    <page name="mainpage"/>
    <page-group key="sub" name="subpath">
      <page name="subpage1" default="true"/>
      <page name="subpage2"/>
    </page-group>
  </page-group>
  <!-- additional parts of a sitemap can be included, e.g. from modules -->
  <config-include file="conf/sitemap-fragment.xml" section="sitemap" module="*"/>
</sitemap>

Internationalizing page names can be done by creating additional language specific alias mappings. These mappings are defined in the sitemap-alias.xml configuration files.

<sitemap-aliases lang="de">
  <!-- the alias elements map page names to language specific display names -->
  <alias page="Home">Start</alias>
  <alias page="Overview">Uebersicht</alias>
  <alias page="Info">Information</alias>
  <alias page="Cities">Staedte</alias>
  <alias page="NationalParks">Nationalparks</alias>
  <alias page="Mountains">Berge</alias>
</sitemap-aliases>

4.3.5. ContextXMLService configuration file

The ContextXMLService handles requests for all pages (where page means some content generated by an XSL transformation). The name of the configuration file can be arbitrarily chosen and is configured in the project configuration file.

This service uses a configuration file that has a special syntax. However properties and customization in this file work nearly the same way as explained for the standard property definitions.

  <context-xml-service-config
    xmlns="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config"
  >
    <global-config>
      <force-ssl>false</force-ssl>
        

force-ssl can be set to true in order to enforce a secure connection for all pages of this service. The whole node is optional and defaults to false.

      <defaultstate class="a.state.Class"/>
      <defaultihandlerstate class="another.state.Class"/>
      <!-- optional attribute for both elements: parent-bean-ref="..." -->
        

defaultstate and defaultihandlerstate are both optional. The class attribute must be given. a.state.Class should de a descendant of de.schlund.pfixcore.workflow.app.StaticState and another.state.Class should be a descendant of de.schlund.pfixcore.workflow.app.DefaultIWrapperState (unless you really know what you are doing). They are used to set the defaults for the state tag used when processing the pagerequest tag (see there for more info).

[Note]Note

As Pustefix creates a singleton-scoped Spring bean of the specified class for each page, which uses a default state, you can't inject dependencies using the Spring XML configuration. That's why Pustefix supports the referencing of parent bean definitions using the parent-bean-ref attribute. Thus it's possible to indirectly inject dependencies into the concrete instances by injecting them into the parent bean definition.

    </global-config>
    
    <context defaultpage="APageName" synchronized="true">
        

Attribute Description
defaultpage Either defaultpage attribute or defaultpage element must be set. Must reference a valid pagerequest.
synchronized Optional. Defaults to true. If set to true, only one request per session is handled concurrently. If set to false all requests will be handled concurrently, requiring thread-safe business logic.

      <defaultpage>
        <variant name="VARIANTNAME">PAGENAME</variant>
        ...
        <default>PAGENAME</default>
      </defaultpage>
        

The defaultpage element can be used if you want to define multiple defaultpages for different variants. [Since: 0.13.1]

      <resource class="A_Resource">
      <!-- optional attributes: bean-name="A_Name" scope="prototype|request|session|singleton"/> -->
        

class is mandatory, can be any Java class, that can be created with a default constructor. The scope attribute is optional and defines the scope in which the Spring bean representing the resource is instantiated (session scope by default). The bean-name attribute is optional and specifies the name of the Spring bean that is created for this resource. There may be multiple resource tags given.

        <implements class="A_Interface">
        

The whole implements node is optional. class is mandatory, must be a Java interface implemented by the resource. There may be more than one implements tag for a resource, but each interface must be unique in the whole context. In other words: it's possible for a resource to implement more than one interface, but not possible for one interface to be implemented by two resources used in the same Context definition.

        </implements>
        <properties>
        

The whole node is optional.

          <prop name="A_Name">A_Value</prop>
        

prop is mandatory and can be used multiple times. It's similar to the use as a child of pagerequest/properties, but used here to create properties that are related to a context resource implementation. The resulting property looks like this: context.resourceparameter.A_Resource.A_Name=A_Value Customization tags may be used around a property to make it depend on a certain makemode or other parameters.

        </properties>
      </resource>
    </context>
    
    <global-output>
 

The whole section is optional. You can list an arbitrary number of resources here. The node attribute value has to be unique within this section and the output section of all pagerequest elements.

        <resource node="AName" bean-ref="aBeanName"/>
        

Attribute Description
class Mandatory (if bean-ref is not present). class is one of the ContextResources defined via implements above.
bean-ref Mandatory (if class is not present). Specifies the bean name of the resource that should be included in the output tree.
node Mandatory. node is the node in the output tree ("/formresult/AName") under which the ContextResource inserts it's data.

    </global-output>

    <interceptors>
      <start>
        <interceptor class="mypackage.MyInterceptor"/>
        <interceptor bean-ref="myInterceptor"/>
      </start>
      <end> ... </end>
      <postrender> ... <postrender>
    </interceptors>
        

Within the interceptors section you can configure ContextInterceptors grouped by the according interception points (for details see Section 6.6, “ContextInterceptors”).

    <scriptedflow name="AName" file="path/to/scriptfile.xml"/>
        

There may be an arbitrary number of scriptedflow tags, but each one must have a unique name. Scripted flows are a special method to control a session and do automatic requests based on initial user input.

    <role name="A_ROLE"/>
    <condition id="A_CONDITION"/>
    <authconstraint id="AN_AUTHCONSTRAINT"/>
        

You can define an arbitrary number of roles, conditions and authconstraints here, for details see Section 7.6, “Authentication and authorization”.

    <preserve-params>
      <param name="myparam"/>
      <!-- <config-include file="path/to/config-fragments.xml" section="preserve-params"/> -->
    </preserve-params>
         

Sometimes request parameters should be preserved when redirecting after a request has been processed, e.g. for passing tracking information. Here you can configure a global list of such parameters.

    <disable-pageflow-passthrough/>
         

Pustefix by default passes through the name of the last pageflow using the __lf URL parameter, thus indicating the preferred pageflow when a page is part of multiple flows. You can get rid of this parameter (e.g. for SEO reasons) by disabling the passthrough, which lets Pustefix store the name of the last flow in the session instead. You should be aware that disabling the passthrough can result in a different pageflow chosen under certain conditions (search for PAGEFLOW_PASSTHROUGH_DIFF in pustefix-servlet.log to find out these cases before disabling the passthrough).

    <pageflow name="AName" final="APageName" stopnext="true|false">
         

There may be multiple pageflow tags defined, but you need at least one (which must be referenced by the defaultflow attribute above). We only describe the normal case without using variants. See here for more information on how to handle variants of pageflows.

Attribute Description
name Mandatory. Must be a unique name.
final Optional, must reference a page with a valid pagerequest definition given in this property file. There may be many pageflows defined for a servlet. A page may well be used in more than one pageflow.
stopnext Optional, defaults to false. If given and true, the pageflow will stop at the next accessible page after the current page even if this page would normally be skipped in the workflow because it doesn't need any input.

      <flowstep name="AnotherPageName" stophere="true|false">
        

Attribute Description
name Mandatory. Must reference a valid pagerequest. Usually there are many flowsteps defined in a pageflow.
stophere Optional, if true the pageflow will stop at this step unconditionally if the submit originated from a step that's before this one in the pageflow. See also the stopnext attribute of the tag which is quivalent to specifying stophere="true" for every single flowstep.

        <oncontinue applyall="true|false">
        

This tag (which is optional) starts a sequence of test/action pairs. The tests are XPath expressions which work on the DOM tree as produced by the flowstep's associated state (note that the navigation is not inserted into the DOM tree at this stage, and the /formresult/formvalues and /formresult/formerrors paths are also not present). The pageflow system calls the tests whenever a state returns a ResultDocument (before it continues with other stuff e.g. a pageflow run). The applyall attribute is optional. If given and true, all actions with matching conditions are executed, if not given or false (the default) only the first action with a matching condition is executed.

          <when test="A_XPath_Expression">
        

The when tag contains the XPath expression to try in it's test attribute. If this attribute is omitted, the whole condition is considered to be true.

            <action type="jumpto" page="APage" pageflow="APageFlow">
        

The action tag denotes the FlowStepAction to execute. The type attribute is mandatory and defines the special action to use. The string jumpto denotes the special FlowStepAction de.schlund.pfixcore.workflow.FlowStepJumpToAction which is used to set the jumptopage (defined via the page attribute) and/or the jumptopageflow (defined via the pageflow attribute).

            </action>
          </when>
          <when test="A_XPath_Expression">
            <action type="A_FlowStepAction" somekey="somevalue">
        

If the type attribute is not jumpto, the value is interpreted as a class of type de.schlund.pfixcore.workflow.FlowStepAction. There can be an arbitrary number of additional attributes (somekey in this example) which are supplied as named parameters to the special FlowStepAction.

            </action>
          </when>
        </oncontinue>
      </flowstep>
    </pageflow>
    
    <pagerequest name="APageName" copyfrom="APageName">
        

Attribute Description
name Mandatory. It must be the name of a page defined in the corresponding depend.xml file.
copyfrom Optional. If given, and set to the name of a valid pagerequest, all configuration from this referenced pagerequest are used for the current page, disregarding all configuration that is made in this pagerequest. It's a plain and simple copy, no extending, no restricting!

      <defaultflow flow="FlowName"/>
        

This node is optional. If a page is part of multiple pageflows, here you can set the default pageflow, which should be chosen if no other pageflow is specified.

      <force-ssl>true|false</force-ssl>
        

The node is optional. If given, and the content is set to true, the page will only run under SSL when jumped to via a link or a submit of form data. If the session currently does not run under SSL, the system will make sure to redirect to a secure session prior to handling the request. After a session is running under SSL, there is no way back (so all other pages will run securely regardless if they have a ssl node or not). You can wrap this tag within a customization element to force use of SSL only in certain modes (e.g. prod mode).

[Note]Note

You can force the servlet as a whole to run only under SSL by specifying the ssl subnode of the servletinfo node.

  <state class="AClassName"/>
  <!-- Alternative usage forms:
    <state class="..." bean-name="..." scope="..."/>
    <state class="..." parent-bean-ref="..."/>
    <state bean-ref="..."/> 
  -->
        

The whole node is optional. If given, the class attribute must be the name of a java class implementing the de.schlund.pfixcore.workflow.ConfigurableState interface. The used State is determined as follows:

  1. If state is given, use the value of it's class attribute. When no scope attribute is present, singleton-scope is used by default.

  2. If the pagerequest has an input child, use the value of the class attribute of the defaultihandlerstate tag explained above if it is given. If this is not given, just use de.schlund.pfixcore.workflow.app.DefaultIWrapperState. Else:

  3. use the value of the class attribute of the defaultstate tag explained above if it is given. If this is not given, just use de.schlund.pfixcore.workflow.app.StaticState.

You can use the scope attribute to specify the scope in which the Spring bean created for this page will be instantiated. You may specify the bean-name attribute to use a fixed name for the automatically created bean. You can use any BSF-supported scripting language for writing your State-implementation, too. Use script:path/to/script for the class attribute. Alternatively you can use an existing Spring bean that implements the de.schlund.workflow.State interface. Use the bean-ref attribute to specify the name of the bean. However the pagerequest may not contain any configuration if you are using a Spring bean.

[Note]Note

If you're referencing a custom Spring bean using bean-ref, Pustefix can't automatically inject the configuration. So if you're bean is extending the DefaultIWrapperState or StaticState it might not work as you expected (or not at all) due to the lack of configuration.

That's why Pustefix supports the referencing of parent bean definitions using the parent-bean-ref attribute. Thus it's possible to indirectly inject dependencies into the concrete instances by injecting them into the parent bean definition. So, despite the fact, that the beans are automatically created by Pustefix, you're able to inject your own dependencies using the Spring XML configuration.

      <input premvc="false|true" policy="ANY|ALL|NONE" protected="false|true">
        

The whole node is optional. It may only be given for a State that is either de.schlund.pfixcore.workflow.app.DefaultIWrapperState or a descendent of it!

The attribute premvc is optional (default is false). It specifies if the IHandlers should be executed before a matching RequestMapping method is called (RequestMapping is called first by default).

The attribute policy is optional (default is ANY). The policy decides when a whole page is considered to be accessible:

  • ANY: just one of the associated handlers needs to be active for the page to be accessible.

  • ALL: all the associated handlers must be active for the page to be accessible.

  • NONE: none of the associated handlers needs to be active for the page to be accessible.

If one of the associated handlers returns false on calling prerequisitesMet(), the page is of course still inaccessible.

The attribute protected is optional (default is false). If set to true submits are only allowed using the POST method together with a secure synchronizer token to prevent CSRF attacks.

        <wrapper prefix="AName" class="AClassName" checkactive="true|false" tenant="aTenant"/>
        

[Caution]Caution

Note: The tag name wrapper can also be called interface with the same allowed attributes for backwards compatibility reasons. This ambiguity may be removed in some future version.

There can be many wrapper nodes for a page. Each one references an "atomic" functional entity consisting of an IWrapper java class (usually autogenerated from a .iwrp xml file that defines the type and names of the parameters passed between the UI and the functional entity and an associated IHandler java class that uses the IWrapper to retrieve the passed parameters via typed getter methods. There may be an optional scope attribute, which specifies the scope in which the handler associated with the wrapper will be instantiated.

Attribute Description
prefix Mandatory. The prefix defines a name for the IWrapper and in effect a namespace for the IWrapper's parameters. If the prefix "bar" is defined for an IWrapper that contains a parameter called "Foo", the submitted HTTP parameter must be called bar.Foo.
class Mandatory. Must be the name of a java class implementing de.schlund.pfixcore.generator.IWrapper. This implicitly defines a de.schlund.pfixcore.generator.IHandler, as every IWrapper knows it's associated IHandler and can be queried for it.
checkactive Optional, default is true. The IHandler method isActive() is NOT called on handlers with checkactive set to false. In other words: the handler is ignored when the system tries to find out if the page is accessible or not. See also the comment for the policy attribute above.
[Caution]Caution

Replaces the activeignore attribute since version 0.13.1. You should be aware that setting activeignore to true now complies with setting checkactive to false. Support for the activeignore attribute will be removed in future versions.

tenant Optional. Can be used to specify IWrappers for a certain tenant only.

      </input>
        

      <process>
        

The process node holds a list of actions, which can be referenced from the UI when submitting forms or using GET requests to transmitt data. These actions group IWrappers into two groups: those that should have their handleSubmittedData() method called, and those that should have their retrieveCurrentStatus() method called when a submit has been handled sucessfully (and the same page is redisplayed). The idea beind the latter is, that sometimes you want to update the submitted form data to some canonical form (e.g. adresses or similar), so you don't want to see the exact same input in the form elements as you have submitted it, but some changed values. In other cases, submitting data to one wrapper may change the values of the form elements of another wrapper - in this case the second wrapper needs to be listed under the <retrieve> node.

        <action name="a_name">
          <submit>
            <wrapper ref="a_prefix_1"/>
            <wrapper ref="a_prefix_2"/>
            ...
          </submit>
          <retrieve>
            <wrapper ref="a_prefix_1"/>
            <wrapper ref="a_prefix_X"/>
            ...
          </retrieve>
        </action>
        <action name="another_name">
        ...
        </action>
      </process>
      
      <output>
        

The whole node is optional. Every page using a State that is itself or a descendant of de.schlund.pfixcore.workflow.app.StaticState can use this. You can have as many resource childnodes as you like.

        <resource node="AName" class="AClassName"/>
        

Attribute Description
class Mandatory (if bean-ref is not present). class is one of the ContextResources defined via implements above.
bean-ref Mandatory (if class is not present). Specifies the bean name of the resource that should be included in the output tree.
node Mandatory. node is the node in the output tree ("/formresult/AName") under which the ContextResource inserts it's data.

      </output>
      
      <properties>
        

The whole node is optional.

        <prop name="APropertyKey">AValue</prop>
        

The node is mandatory and can be used multiple times. It will be transformed into a java property that is associated to the page. There are some props that are already defined for de.schlund.pfixcore.workflow.app.StaticState and descendants. These are listed below

Property Name Property Value Description
mimetype e.g. text/css If given, sets the mimetype of the HttpResponse object to something else than the default text/html. This is most often used for text/css.
responseheader.A_HEADER A_VALUE If given, set the header A_HEADER of the HttpResponse object to A_VALUE. NOTE: the Pustefix system uses a set of default headers that are only used, when no user defined headers are given! The set of default headers is: Expires=Mon, 26 Jul 1997 05:00:00 GMT Cache-Control=private If you want to use some of them in addition to your own headers, you must manually supply them, too.

      </properties>
    </pagerequest>
    
    <config-include file="conf/myfile.xml" section="pagerequests" module="mymodule"/>
        

Includes a part of a config fragments file at this location. See Section 4.3.8, “Configuration Fragments” for details on how to define config fragments.

Attribute Description
file Mandatory. Path to the file that contains the tags to be included (relative to docroot).
section Optional. Type of the section that shall be included. If more than one section of the specified type exists in the file, the content of all this sections is included.
refid Optional. Include a section identified by the specified id. The refid specified here must match the id attribute of exactly one section in the specified file.
xpath Optional. A XPath expression specifying the node-set to be included. The prefixes to be used for XML namespaces are "fr" for the namespace of the fragments file tags and "pr" for the namespace of the ContextXMLServlet configuration tags.
module Optional. Specifies the Pustefix module where the file is located (by default the file is searched in the webapp itself). You can use simple name patterns to include all or a subset of modules availabe in the classpath (e.g. "*" or "mod-*-us").

One and only one of the section, refid or xpath attribute has to be specified for each config-include.

    <properties>
      <prop name="AProperty">AValue</prop>
        

Property Name Property Value Description
mimetype e.g. text/css If given, sets the mimetype of the HttpResponse object to something else than the default text/html. This is most often used for text/css.
responseheader.A_HEADER A_VALUE If given, set the header A_HEADER of the HttpResponse object to A_VALUE. Headers set here can be overwritten for specific pages. NOTE: the Pustefix system uses a set of default headers that are only used, when no user defined headers are given! The set of default headers is: Expires=Mon, 26 Jul 1997 05:00:00 GMT Cache-Control=private If you want to use some of them in addition to your own headers, you must manually supply them, too.

You can also specify properties here that are understood by the AbstractPustefixRequestHandler and AbstractPustefixXMLRequestHandler classes.

    </properties>
  </context-xml-service-config>
        

4.3.6. DirectOutputService configuration file

Occasionally you don't want to generate output with an XSLT Transformation, but e.g. deliver binary content directly to the output stream instead. In this case you can use the DirectOutputService. The name of the configuration file can be arbitrarily chosen and is configured in the project configuration file.

The service knows about one or many directoutputpagerequests. For the XML/XSLT side of things, they look like normal pages (in fact, the value of the directoutputpagerequest's name attribute must be a page defined in the navigation section of depened.xml. Of course, no target definition has to be given, only the page in the navigation structure must exist). But other than the usual pagerequest, a directoutputpagerequest has an associated directoutputstate whose class attribute is a java class implementing de.schlund.pfixcore.workflow.app.DirectOutputState.

  <direct-output-service-config
    xmlns="http://pustefix.sourceforge.net/2004/properties"
  >    
    <global-config>
      <force-ssl>false</force-ssl>
        

See the comment for the global-config node in Section 4.3.5, “ContextXMLService configuration file”.

    </global-config>
    
    <authconstraint ref="AN_AUTHCONSTRAINT"/>
         

You can reference an authconstraint from the context configuration, which has to be fulfilled to access a page. This default authconstraint can be overridden for single pages. If no default authconstraint is set here, the context's default authconstraint will be used. If no authconstraint is set at all, a page requires no authentication.

    <config-include file="conf/myfile.xml" section="directoutputpagerequests" module="mymodule"/>
         

Includes a part of a config-fragments at this location. See Section 4.3.8, “Configuration Fragments” for details on how to define config fragments.

Attribute Description
file Mandatory. Path to the file that contains the tags to be included (relative to docroot).
section Optional. Type of the section that shall be included. If more than one section of the specified type exists in the file, the content of all this sections is included. For a DirectOutputServlet configuration only directoutputpagerequests and properties are valid.
refid Optional. Include a section identified by the specified id. The refid specified here must match the id attribute of exactly one section in the specified file.
xpath Optional. A XPath expression specifying the node-set to be included. The prefixes to be used for XML namespaces are "fr" for the namespace of the fragments file tags and "d" for the namespace of the DirectOutputService configuration tags.
module Optional. Specifies the Pustefix module where the file is located (by default the file is searched in the webapp itself). You can use simple name patterns to include all or a subset of modules availabe in the classpath (e.g. "*" or "mod-*-us").

One and only one of the section, refid or xpath attribute has to be specified for each config-include.

    <directoutputpagerequest name="APageName">
      <directoutputstate class="AClassName"/>
        

The class specified for the directoutputstate must implement the de.schlund.pfixcore.workflow.DirectOutputState interface. The tag may have an optional scope attribute which specifies the scope in which the corresponding state should be instantiated. There may also be an optional bean-name which, if present, will be used as the name of the Spring bean created for this direct output state. Instead of the class attribute, you may specify a bean-ref attribute which has to reference a Spring bean defined in the spring.xml file for this project. In this case, no Spring bean will be created but the existing bean will be used instead.

      <authconstraint ref="AN_AUTHCONSTRAINT"/>
        

You can optionally reference an authconstraint from the context configuration to override the default authconstraint.

      <properties>
        

The whole properties node is optional.

        <prop name="APropertyKey">AValue</prop>
        

The node is mandatory and can be used multiple times. It will be transformed into a java property that is associated to the page. The java property that is constructed will look like this: pagerequest.APpageName.APropertyKey=AValue where APageName is the value of the name attribute.

      </properties>
    </directoutputpagerequest>
  </direct-output-service-config>
        

4.3.7. WebServices

The configuration of AJAX / webservices is described in the corresponding section.

4.3.8. Configuration Fragments

Configuration fragments files contain aggregated configuration directives that are intended to be reused in different configuration files.

<fr:config-fragments
  xmlns:fr="http://pustefix.sourceforge.net/configfragments200609"
  xmlns:c="http://www.pustefix-framework.org/2008/namespace/context-xml-service-config"
  xmlns:d="http://www.pustefix-framework.org/2008/namespace/direct-output-service-config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://pustefix.sourceforge.net/configfragments200609
                      http://pustefix.sourceforge.net/configfragments200609.xsd">
  
  <fr:navigation id="nav1">
      

All sections have an optional id that can be used to identifiy the section when more than one section fo the same type is present in one file. The value of the id attribute has to be unique within the whole file.

    <page name="MyPage"/>
      

The structure here is the same as within the navigation tag of the depend.xml file.

  </fr:navigation>
  <fr:targets>
    <standardpage name="MyPage" xml="xml/mymaster.xml"/>
      

The tags allowed here are the same that are allowed for standardpage or target definitions in the depend.xml file.

  </fr:targets>
  <fr:resources>
    <c:resource class="com.example.MyResourceImpl">
      <pr:implements class="com.example.MyResource"/>
    </c:resource>
      

The tags allowed here are the same that are allowed for the definition of context resources within the context tag of the ContextXMLServlet configuration.

  </fr:resources>
  <fr:interceptors>
    <c:interceptor class="com.example.MyInterceptor"/>
      

The tags allowed here are the same that are allowed within the start, end and postrender interceptor tags of the ContextXMLServlet configuration (for details also see Section 6.6, “ContextInterceptors”).

  </c:interceptors>
  <fr:scriptedflows>
    <c:scriptedflow name="myscript" file="myproject/conf/scriptedflows/myscript.script.xml"/>
      

The tags allowed here are the same that are allowed within the scriptedflows tag of the ContextXMLServlet configuration.

  </fr:scriptedflows>
  <fr:roles>
    <c:role name="MY_ROLE">
      <c:pageaccess names="mypage*"/>
    </c:role>
      

The tags allowed here are the same that are used for role definition in the ContextXMLServlet configuration.

  </fr:roles>
  <fr:pageflows>
    <c:pageflow name="MyFlow">
      <c:flowstep name="MyFirstPage"/>
      <c:flowstep name="MySecondPage"/>
    </c:pageflow>
      

The tags allowed here are the same that are used for the definition of pageflows in the ContextXMLServlet configuration.

  </fr:pageflows>
  <fr:pagerequests>
    <c:pagerequest name="MyPage"/>
      

The tags allowed here are the same that are used for the definition of pagerequets in the ContextXMLServlet configuration.

  </fr:pagerequests>
  <fr:properties>
    <pr:prop name="myproperty">myvalue</pr:prop>
      

The tags allowed here are the same that are allowed within the properties tag of the ContextXMLServlet configuration.

  </fr:properties>
  <fr:directoutputpagerequests>
    <d:directoutputpagerequest name="foo">...</d:directoutputpagerequest>
      

Direct output pagerequests can be defined here. See Section 4.3.6, “DirectOutputService configuration file” for details on this.

  </fr:directoutputpagerequests>
</fr:config-fragments>
        

4.4. Spring configuration and customization

Pustefix is based on Spring MVC. On ApplicationContext startup the Pustefix configuration has to be read and framework specific bean definitions have to be created. This can either be done by using Pustefix's own ApplicationContext implementation org.pustefixframework.container.spring.beans.PustefixWebApplicationContext, or you can alternatively use a standard Spring ApplicationContext and add Pustefix's BeanDefinitionRegistryPostProcessor implementation org.pustefixframework.container.spring.beans.PustefixConfigPostProcessor.

The following example shows how you can configure the Spring ApplicationContext in the web.xml configuration file of your application using the classical XML configuration style:

  <servlet>
      <servlet-name>dispatcher</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
         <param-name>contextClass</param-name>
         <param-value>org.pustefixframework.container.spring.beans.PustefixWebApplicationContext</param-value>
      </init-param>
      <init-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>WEB-INF/project.xml WEB-INF/spring.xml</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
      <servlet-name>dispatcher</servlet-name>
      <url-pattern>/*</url-pattern>
   </servlet-mapping>

The configuration file locations are passed to the DispatcherServlet using the standard contextConfigLocation init parameter. By default Pustefix passes the Pustefix configuration file WEB-INF/project.xml and, if exisiting, the file WEB-INF/spring.xml, which is intended to contain a standard Spring XML bean configuration.

The next example shows how you can configure Spring in web.xml for using annotation configuration and how to add the required BeanDefinitionRegistryPostProcessor in your Java configuration:

   <servlet>
      <servlet-name>dispatcher</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
         <param-name>contextClass</param-name>
         <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
      </init-param>
      <init-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>org.pustefixframework.samples.mvctest.SpringConfig</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
      <servlet-name>dispatcher</servlet-name>
      <url-pattern>/*</url-pattern>
   </servlet-mapping>
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "org.pustefixframework.samples.mvctest")
public class SpringConfig implements WebMvcConfigurer {

    @Bean
    public PustefixConfigPostProcessor registerPustefix() {
        return new PustefixConfigPostProcessor();
    }
}

4.4.1. Customizing the Spring configuration

Spring supports the externalization of property values from Spring XML configuration files into separate standard Java property files. Pustefix extends this mechanism by adding support for the Pustefix XML property file format, which let's you use the choose/when-customization known from other Pustefix configuration files, like factory.xml, for Spring properties too.

Therefor Pustefix by default registers a Spring PropertyPlaceholderConfigurer, which looks for a Pustefix XML property file under WEB-INF/spring-properties.xml and a PropertyOverrideConfigurer, which looks under WEB-INF/spring-properties-override.xml.

The following example shows the two ways you can externalize property values:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">

  <bean id="mybean1" class="mypackage.MyBeanClass">
    <property name="text" value="${default.text}"/>
  </bean>

  <bean id="mybean2" class="mypackage.MyBeanClass">
    <property name="text" value="This is the default text"/>
  </bean>

</beans>

The first bean uses a property placeholder, the second bean already supplies a default value, which should be overwritten by an externalized value.

The following spring-properties.xml file configures the value for the first bean using the Pustefix customization mechanism to provide a mode-dependent value for the property placeholder:

<?xml version="1.0" encoding="UTF-8"?>
<properties xmlns="http://www.pustefix-framework.org/2008/namespace/properties-config" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/properties-config 
                                http://www.pustefix-framework.org/schema/properties-config.xsd">    
    <choose>
      <when test="$mode='prod'">
        <property name="default.text">I'm in production mode</property>
      </when>
      <otherwise>
        <property name="default.text">I'm in development mode</property>
      </otherwise>
    </choose>

</properties>

The following spring-properties-override.xml file overrides the value for the second bean by using the format beanName.property as property name. It also shows how you can use the available environment properties (like fqdn or mode) within a property value:

<?xml version="1.0" encoding="UTF-8"?>
<properties xmlns="http://www.pustefix-framework.org/2008/namespace/properties-config" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/properties-config 
                                http://www.pustefix-framework.org/schema/properties-config.xsd">
    
    <property name="mybean2.text">I'm in ${mode} mode</property>

</properties>

If you want to register your own PropertyPlaceholderConfigurer or PropertyOverrideConfigurer capable of the Pustefix XML property format, e.g. for tests or child ApplicationContexts, you can do so by creating a PustefixPropertiesPersister bean and referencing it from the configurer's propertiesPersister property.

4.4.2. Environment-dependent Spring property files

Pustefix provides a special PropertyPlaceholderConfigurer implementation which can be used to organize properties in own files for each environment, e.g. based on the mode value. You can enable it by adding an according bean definition to the Spring configuration.

<bean id="propConfig" class="org.pustefixframework.container.spring.util.EnvironmentPropertyPlaceholderConfigurer"/>

By default properties will be loaded from WEB-INF/spring.properties and WEB-INF/spring-[MODE].properties, whereas [MODE] will be replaced by the current value of the according context init parameter and mode specific properties will override the general ones.

Here's an example of a more complex non-default configuration, where properties are loaded from multiple locations using multiple context init parameters as discriminator:

<bean id="propConfig" class="org.pustefixframework.container.spring.util.EnvironmentPropertyPlaceholderConfigurer">
  <!-- optional property, default: WEB-INF/spring.properties -->
  <property name="locations">
    <list>
      <value>classpath*:spring.properties</value>
      <value>WEB-INF/spring.properties</value>
    </list>    
  </property>
  <!-- optional property, default: mode -->
  <property name="parameters">
    <list>
      <value>mode</value>
      <value>locale</value>
    </list>
  </property>
</bean>

If the mode param value will be test and locale will be set to en_US, the properties will be loaded from the following locations (same properties in files loaded later will override already read properties, i.e. have higher precedence).

  • classpath*:spring.properties
  • classpath*:spring-en_US.properties
  • classpath*:spring-test.properties
  • classpath*:spring-test-en_US.properties
  • WEB-INF/spring.properties
  • WEB-INF/spring-en_US.properties
  • WEB-INF/spring-test.properties
  • WEB-INF/spring-test-en_US.properties

4.4.3. Environment-dependent Spring profiles

Pustefix by default activates a Spring profile for the current execution environment mode (using the mode name as profile name). Thus you can easily use bean definition profiles or @Profile Java configuration without having to take care of the profile activation.

<beans xmlns="http://www.springframework.org/schema/beans">
   
   <bean name="mybean" class="mypackage.MyBean"/>

   <beans profile="prod">
      <bean name="mybean" class="mypackage.MyBeanAlternative"/>
   </beans>

</beans>

4.4.4. Reserved Spring bean names

Some Spring beans created by the core framework are intended for usage by the application, i.e. are registered with fixed/reserved bean names, which can be referred to inject them into application beans. The following table shows those beans with their associated names and aliases.

Table 4.4. Reserved Spring bean names

Name Alias Scope Description
de.schlund.pfixcore.workflow.Context pustefixContext Session The context facade providing access to request-/session-state and configuration.
de.schlund.pfixxml.TenantInfo pustefixTenantInfo Singleton Bean providing the list of available tenants.


4.5. Logging configuration

Pustefix internally uses SLF4J as its logging framework (since version 0.21, older versions rely on Log4j). Thus Pustefix can be used with all logging libraries supporting SLF4J. The preferred frameworks are Logback and Log4j 2, for which Pustefix includes additional support, like access to Pustefix environment properties within the logging configuration.

4.5.1. Logback

You can optionally use the pustefix-logback module, which brings automatic configuration of required Maven dependencies, extensions for accessing Pustefix environment properties and a sensible Pustefix default logging configuration as reference (or to be included).

<dependency>
  <groupId>org.pustefixframework.logging</groupId>
  <artifactId>pustefix-logback</artifactId>
  <version>${pustefix.version}</version>
</dependency>

You can make Pustefix environment properties available as Logback context variables using a custom EnvironmentPropertyDefiner:

<define name="logroot" class="org.pustefixframework.logging.logback.EnvironmentPropertyDefiner">
  <key>logroot</key>
</define>
<define name="mode" class="org.pustefixframework.logging.logback.EnvironmentPropertyDefiner">
  <key>mode</key>
</define>

<appender name="EXAMPLE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <file>${logroot}/example.log</file>
  ...
</appender>

Or you can use a custom Logback Action to insert Pustefix environment properties as context variables:

<newRule pattern="*/insertFromEnvironment"
         actionClass="org.pustefixframework.logging.logback.InsertFromEnvironmentAction"/>

<insertFromEnvironment name="logroot"/>
<insertFromEnvironment name="mode"/>
<!-- <insertFromEnvironment name="mode" as="mymode" scope="local"/> -->

<appender name="EXAMPLE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <file>${logroot}/example.log</file>
  ...
</appender>

Pustefix provides a default logging configuration which already adds all environment properties and configures according appenders and loggers with mode dependent log levels. You can include this configuration from your application's logback.xml file and add your application specific stuff:

<configuration>
  <include resource="org/pustefixframework/logging/logback.xml"/>
  ...
</configuration>

4.5.2. Log4j 2

You can optionally use the pustefix-log4j module, which brings automatic configuration of required Maven dependencies, extensions for accessing Pustefix environment properties and a sensible Pustefix default logging configuration as reference (or to be included).

<dependency>
  <groupId>org.pustefixframework.logging</groupId>
  <artifactId>pustefix-log4j</artifactId>
  <version>${pustefix.version}</version>
</dependency>

You can make Pustefix environment properties available as Log4j2 configuration properties using a custom Lookup plugin. You have to add the packages attribute as follows, so that Log4j can pick up the Pustefix plugin. Then the Pustefix environment properties can be referenced using their name prefixed with pfx:

<Configuration packages="org.pustefixframework.logging.log4j">
  <Appenders>
    <File name="EXAMPLE" fileName="${pfx:logroot}/example.log">
      ...
    </File>
  </Appenders>
</Configuration>

Pustefix provides a default logging configuration which already adds all environment properties and configures according appenders and loggers with mode dependent log levels. Unfortunately Log4j only supports including configuration using XInclude, and the current JDK/Xerces implementation is so limited, that it's more or less useless.

So you either can copy the default logging configuration and use it as a base, or you can use the XML entity resolving mechanism. Therefor Pustefix also provides a default configuration splitted into according entity files, which can be included using classpath URIs:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configuration [
  <!ENTITY Properties SYSTEM "classpath:org/pustefixframework/logging/log4j-properties.ent">
  <!ENTITY Appenders SYSTEM "classpath:org/pustefixframework/logging/log4j-appenders.ent">
  <!ENTITY Loggers SYSTEM "classpath:org/pustefixframework/logging/log4j-loggers.ent">
]>
<Configuration status="WARN" packages="org.pustefixframework.logging.log4j">

  <Properties>
    &Properties;
    ...
  </Properties>

  <Appenders>
    &Appenders;
    ...
  </Appenders>

  <Loggers>
    &Loggers;
    ...
  </Loggers>

</Configuration>

4.5.3. Log4j (deprecated)

Log4j is the default logging framework used by older Pustefix versions (before 0.21). Meanwhile it's deprecated and only should be used with old Pustefix versions or if you can't change to another logging framework because it's still required by your application.

By default the Log4j configuration is read from WEB-INF/pfixlog.xml. Within this file you can make use of the Pustefix customization tags (see Section 4.2, “Customization tools”).

Often you have to configure different file paths depending on your runtime environment, e.g. for development or production mode. Therefor you can use the customization tag <cus:logroot/> as a placeholder for the logroot ServletContext init parameter, which can be set when deploying your application in the target environment.

<?xml version="1.0" encoding="UTF-8" ?>
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"
                     xmlns:cus="http://www.schlund.de/pustefix/customize">

  <appender name="LOGGER_GENERAL" class="org.apache.log4j.RollingFileAppender">
    <param name="File"><cus:logroot/>pustefix-servlet.log</param>
    <param name="MaxFileSize" value="10MB"/>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d{ISO8601} [%t] %-5p %c [%M():%L] %x - %m\n"/>
    </layout>
  </appender>
  ...
</log4j:configuration>

Alternatively you can make use of Pustefix's <cus:choose>|<cus:when>|<cus:otherwise> customization tags, to branch the configuration depending on the predefined variables, like mode, fdqn or machine, e.g. you can set different log levels for different environments.

  <root>
    <cus:choose>
      <cus:when test="$mode = 'prod'">
        <priority value="WARN"/>
      </cus:when>
      <cus:otherwise>
        <priority value="DEBUG"/>
      </cus:otherwise>
    </cus:choose>
    <appender-ref ref="LOGGER_GENERAL"/>
  </root>

By default Log4j is configured at the beginning of the Spring ApplicationContext creation. But it's better to set up logging at the very beginning of the webapp startup to be able to log errors or messages that occurred before this phase, e.g. errors from other servlets or filters. Therefor Pustefix provides a ServletContextListener, which should be configured as the first listener in the webapp's web.xml.

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jee="http://java.sun.com/xml/ns/javaee">
   <listener>
     <listener-class>org.pustefixframework.http.Log4jConfigListener</listener-class>
   </listener>
   ...
</web-app>

The Pustefix customization mechanism can also be used to modularize the logging configuration. Using the customization tag <cus:include> you can include logging configurations from the classpath or other modules:

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" xmlns:cus="http://www.schlund.de/pustefix/customize">

  <cus:include name="module://pustefix-core/pfixlog.xml"/>
  <cus:include name="classpath:META-INF/pfixlog.xml"/>
  
  <!-- additional or overriding appenders/loggers -->

</log4j:configuration>

Such includes can be placed as top-level elements in your logging configuration. Included configurations can contain includes themselves. If loggers/appenders are configured multiple times, the last occurrence will have precedence. Thus you can later override settings included before, e.g. if you include the default configuration from the Pustefix framework, you can override application specific settings afterwards (see example above).

Chapter 5. Core Pustefix tag library

Pustefix includes a small library of tags defined as XSLT templates which implement low level functionality common to all Pustefix applications. These templates are mostly concerned with creating links to Pustefix pages or external URLs, sending data via HTML forms (including the necessary error handling) and including XML ressources (Include Parts).

All core tags reside in their own namespace. The prefix usually used is pfx, and the namespace is http://www.schlund.de/pustefix/core. You are not supposed to enter your own private project specific tags into this namespace.

The following table lists these tags together with a very short explanation of what they do. Refer to the relevant subsections below to find a detailed explanation on their relevant attributes, subnodes and how to use them.

Table 5.1. The Core Pustefix XSLT Tags

Tag name Short information
pfx:document The top-most container for all Pustefix pages, see Section 5.1, “Defining the structure of a document”
pfx:frameset, pfx:frame Used to define pages with framesets and frames, see Section 5.1, “Defining the structure of a document”
pfx:button This tag creates simple links to internal Pustefix pages (possibly submitting parameters for requests), see Section 5.2.1, “pfx:button”
pfx:url Used to create only the content of the href attribute of a link to an internal page, see Section 5.2.2, “pfx:url”
pfx:elink This tag creates links to external URLs where care must be taken to strip the session ID from the referer header to not leak sensitive information to the outside world, see Section 5.2.3, “pfx:elink”
pfx:include This tag references a file via its href attribute, and includes a named snippet of xml content contained in this file via the part attribute, see Section 5.3.1, “Include parts (<pfx:include>)”
pfx:maincontent This tag is used to include "computed" include parts, see Section 5.3.2, “Generated include requests (<pfx:maincontent>)”
pfx:image This tag references images to be included in the final page (via img-tags), see Section 5.3.3, “Displaying images (<pfx:image>)”
pfx:forminput This tag creates a HTML form, see Section 5.5.1, “Form creation”
pfx:xinp Used to create HTML form elements, see Section 5.5.4, “Form elements”
pfx:checkfield This tag supplies content depending on the error state of a special form field, see the section called “Errors attached to a field”
pfx:checkerror, pfx:checknoerror Used to check for the presence of any error condition, see the section called “Errors attached to a field”
pfx:checkmessage Used to check for the presence of any page message, see the section called “Checking for pagemessages”
pfx:checkactive, pfx:checknotactive These tags check for visibility (or not) of pages or for the activity (or not) of handlers, see Section 5.6.1, “Checking page status”
pfx:themeselect, pfx:langselect Used to select content depending on a matching theme or currently selected language, see Section 5.6.3, “Displaying content based on the theme” and Section 5.6.2, “Displaying content based on the language”
pfx:editconsole, pfx:webserviceconsole create panels of shortcut links useful during development, see Section 5.6.4, “Using the Pustefix console”


5.1. Defining the structure of a document

This section describes the format for those documents serving as the structure defining xml source of the finally transformed documents. These can be found in the xml subdirectory of your project.

The explanation keeps an eye on the expected usage patterns of these documents.

There are basically two kinds of "pages" you deliver with Pustefix.

  • Pages that have no frames and may deliver html or any other text based format.

  • Pages that contain an arbitrary amount of frames and framesets. Those usually deliver html.

5.1.1. Structure of a Type 1 document

For a html delivering page without frames:

<pfx:document xmlns:pfx="http://www.schlund.de/pustefix/core">
  <html>
    <!--
      Any content valid for an html document
    -->
  </html>
</pfx:document>

If you don't want to deliver html, just omit the <html> tag. The following could be used to implement a CSS stylesheet.

<pfx:document>.foo { color: #ffff00; font-family: Helvetica; }</pfx:document>

The rule of thumb is: Whatever you put between <pfx:document> is up to you and will be delivered just as you write it there. Just remember that the <html> is not automatically inserted for you, you have to write it yourself.

5.1.2. Structure of a Type 2 document

There are only subtle differences. A document is a Type 2 doc by definition whenever there is a <pfx:frameset> and possibly a <head> node as the only direct children of <pfx:document>.

<pfx:document xmlns:pfx="http://www.schlund.de/pustefix/core">
  <head>
    <!--
      Again, put anything you want to appear in the head of the _top frame! This means
      page title, script stuff or stylesheets.
    -->
  </head>
  <pfx:frameset rows="20,*">
    <pfx:frame name="navi">
      <html>
        <head>...</head>
        <body>
          <!-- Any HTML content -->
        </body>
      </html>
    </pfx:frame>
    <pfx:frame name="main">
      <html>
        <body>
          <!-- Any HTML content -->
        </body>
      </html>
    </pfx:frame>
  </pfx:frameset>
</pfx:document>

As you can see there is NO <html> tag just below <pfx:document>. This is the one important difference between Type 1) and Type 2). As a rule you could say that you only have to insert the <html> yourself wherever the "real" content is. In a Type 1) doc this is the whole content of the <pfx:document> tag, so we need to set it there. But for a Type 2) doc, the "real" content is the content of the <pfx:frame> tags, so you need to set it there.

5.2. Creating links to internal and external pages

Pustefix provides tags that allow you create links to internal and external pages.

5.2.1. pfx:button

The <pfx:button> tag is responsible for generating links to other pages inside the pustefix environment. In fact, it not always creates a link, but depending on the fact if the target page is accessible ("invisible") or not, or if the target page is the same as the current page ("active", aka "the target page is already active") it can display completely different content, and only when the target page is accessible and is different from the current page, a <a href="...">...</a> is put around it.

The template takes care of constructing the correct url with session information embedded and builds up valid, url encoded query strings.

<pfx:button page="APage" pageflow="AFlow" jumptopage="APage" jumptopageflow="AFlow" 
            forcestop="true|false|step" startwithflow="true|false" altkey="pageAlternativeKey" path="sub/path">
  <!-- Control the submit commands -->
  <pfx:command page="APage" name="SUBWRP">prefix</pfx:command>

  <!-- Supply additional parameters -->
  <pfx:argument name="AName">AValue</pfx:argument>
  
  <pfx:anchor frame="AFrame">AnAnchor</pfx:anchor>
  
  <!-- Select a page at runtime -->
  <pfx:page>pageName</pfx:page>

  <!-- Select a page alternative by key --> 
  <pfx:altkey>pageAlternativeKey</pfx:altkey>

  <!-- Set page-relative URL sub path (suffixing the display page name). Segments are URI-encoded automatically. -->
  <pfx:path>sub/path</pfx:path>
  <pfx:path>
    <pfx:segment>sub</pfx:segment>
    <pfx:segment>path</pfx:segment/>
  </pfx:path>

  <!--
    These three optional child nodes can be used to display different
    content depending on the situation:
  -->
  <pfx:invisible>
    <!-- Displayed when link is not accessible -->
  </pfx:invisible>
  <pfx:normal>
    <!-- Displayed when link is accessible -->
  </pfx:normal>
  <pfx:active>
    <!-- Displayed when current page == link target -->
  </pfx:active>
  <!--
    Displayed link content
  -->
</pfx:button>

The <pfx:button> tag supports the following attributes:

Table 5.2. Attributes of the pfx:button tag

Attribute name Mandatory? Description
page optional defaults to the current page. Used to give the target page where the link points to. Note: leaving this empty also implies mode="force".
pageflow / jumptopage / jumptopageflow / forcestop optional These attributes work the same as for form submit controls
altkey optional Select page alternative by key
startwithflow optional

Defaults to false. When set to true, the request will not go to a page directly, but start with the a processing of the chosen pageflow to determine the page to use.

The meaning of the page attribute also changes: If the submitted page is part of the chosen pageflow, the flow will be queried for the page to use up to the point in the flow where the given page is, which is then used in any case. In other words, this constitutes an end point for the search of a matching page in the flow.

mode optional

Default is empty. When set to force, a link is created and the matching CSS is used even in the active button state, i.e. whenever the target page is the current page.

When set to desc, the button state is not only active when the current page == target page, but also when the current page is a descendent page of the target page.

nodata optional Default to false. Normally, whenever you use a pfx:argument tag to attach parameters to the query string, the system automatically also adds the parmeter __sendingdata=1 to the query string, thereby signalling to the backend system, that it should process incoming data. Set this attribute to true to prohibit this behaviour.
frame / target optional Works the same as for submit controls
normalclass / activeclass / invisibleclass optional defaults are: core_button_normal, core_button_active and core_button_invisible. These three attributes define the CSS classes to be used for the three different states of a pfx:button

Commands and arguments

It is possible to use the same children to control the submit behaviour as it is done with form controls.

Link contents

The pfx:button template allows you to change the link content depending on the status of the target page.

  • Content of pfx:invisible will be only displayed when the target page is not accessible.

  • Content of pfx:active will be only displayed when the target page is the current page.

  • Content of pfx:normal will be only displayed when the target page is different from the current page and when it's accessible.

Content outside of these tags will be used in any case. If you only want to have different content for the invisible case, just put the content for the active and normal case inside pfx:normal, and add a pfx:invisible child with the differing content. The content of a pfx:normal node serves as the fallback for the other two cases.

[Note]Note

Note that only in the normal (regardless if the content comes from a dedicated pfx:normal child node or not) a link is put around the generated content.

Note also, that for differences between the three cases that can be expressed with CSS, you don't need to use these special child nodes. The system makes sure to use the three associated classes explained above to allow styling.

It is also possible to change to content, depending on whether a page has already been visited or not.

  • Content of pfx:visited will be only displayed when the link has been already visited at least once in this session.

  • Content of pfx:visited will be only displayed when the link has not been visited in this session.

The two tags above may also be put inside pfx:normal and pfx:invisible tags to express different content for accessible (or inaccessible) pages depending on the fact if they have been visited at least once already.

This makes of course not much sense with pfx:active, because a page where this applies is always the current page and by that is always visited.

5.2.2. pfx:url

This tag takes mostly the same attributes as <pfx:button>, but it only creates the URL and does not build up any content or generate a whole link. You can use this template if you just need the pure URL string.

5.2.3. pfx:elink

When creating links to external URLs care must be taken to ensure that no sensitive data (especially the session ID) leaks into log files of remote servers via the referer header. To make sure that this can't happen, all links to external sites must be propagated via a special servlet, the de.schlund.pfixxml.DerefServer.

Every Pustefix project has this servlet configured to be accessible under the path /deref. To make the handling of external URLs easier, there also exists a special tag <pfx:elink> that automates the creation of the correct link.

<pfx:elink href="http://some.host/location" target="_popup|SomeName">
  <!--
    Optional, use the <pfx:host> child instead of the href attribute whenever you
    need to construct the URL with additional code (e.g. from data only available
    at runtime)
  -->
  <pfx:host>...</pfx:host>

  <!--
    Optional, use as many of <pfx:argument> tags as you need to supply the
    parameters for the query string.
  -->
  <pfx:argument name="SomeName">...</pfx:argument>

  <!--
    Place content of the link here
  -->
</pfx:elink>

5.3. Including text and images

There are two types of ressources that need to be included into a Pustefix page. Textual content ("Include Parts") is included with the help of the <pfx:include> tag while images are included via the <pfx:image> tag. Both tags make sure to register the ressources in the runtime system, so at all times the system knows which ressources a certain page uses. This information is used to check if the page is still up-to-date or needs rebuilding (by comparing file modification times of the ressources with the creation time of the page itself).

Of course this check can be disabled for a "live" system, as there is typically no need to check for changed ressources.

5.3.1. Include parts (<pfx:include>)

Include parts contain the content that is displayed on your pages. The parts are organized into include files. Every part has the same structure:

The children of the part tag are theme tags (at least one). The name attribute of the theme tag is the name of a theme as it is defined in the projects depend.xml.in file. Often these themes are just the project name or "default", which is used as the fallback when no more specific theme name matches (see here on how to define themes in the depend.xml.in file).

[Note]Note

Earlier versions of Pustefix had no special themeing, the only thing that was used was the project name itself and "default" as the fallback. Still today, the default value for the "themes" attribute in the root node of the depend.xml.in file (when not given explicitely) is just "<ProjectName> default", which makes the new system behave exactly as the old one did.

The resolution of the matching theme is done at the time the part is included (see below). Every page "knows" which themes are defined for it, and therefore it is possible to decide which product branch to use on generation time. The language on the other hand can be changed dynamically while the user clicks through the application, so the selection of the right language subtree (if more than one is present) is done at runtime.

<include_parts>
  <part name="Foo">
    <theme name="default">
      <pfx:langselect>
        <pfx:lang name="default">
          <!--
            The default content of part Foo goes here...
          -->
        </pfx:lang>
        <pfx:lang name="en_GB">
          <!--
            Default content in british english goes here...
          -->
        </pfx:lang>
        <pfx:lang name="en_*">
          <!--
            Default content in any other english language goes here...
          -->
        </pfx:lang>
      </pfx:langselect>
    </theme>
    <theme name="Theme_A">
      <!--
        The default content for theme Theme_A goes here...
      -->
    </theme>
  </part>

  <part name="Baz">
    <!-- Other parts -->
  </part>
</include_parts>

A part is referenced with two attributes: The filename of the include file that contains it, and the name of the part. The filename can be omitted, if you're referencing a part within the same file.

<pfx:include href="MyProject/txt/MyIncludefile" part="Foo" noerror="true|false" noedit="true|false"/>

Table 5.3. Attributes of the pfx:include tag

Attribute name Mandatory? Description
href optional If not given, it defaults to the current include part file. If it's not given and the module attribute is set, the path of the current include part file will be used as path within the module (omitting the first path component if the current file isn't already from a module).
part mandatory The name of the part to include
noerror optional Defaults to false. Set this to true to imply that no warning sign should be generated when the include is not found. Only set this when you know what you do.
noedit optional Defaults to false. Set this to true to imply that this include part should not be editable via the pustefix editor. Only set this when you know what you do.
level optional Not set by default. Set this to runtime if the part should be included at runtime (on the last transformation level).
search optional Not set by default. Set this to dynamic if a matching part should be dynamically searched. The search order is: current project, common folder, and if a module is specified: overriding module, specified module. See Section 7.5, “Dynamic resource inclusion”.
module optional Not set by default. Set this to the name of the module from which the part should be loaded. If dynamic search is set, the module just serves as fallback location if the part can't be found in the project or common location and isn't overrided by another module. See Section 7.5, “Dynamic resource inclusion”. You can also use one of the pre-defined special module names (see Special module names).


Using this tag results in the matching product branch of the include part to be inserted in place of the tag.

If you're deferring an include until runtime using level="runtime", you can also defer the creation of the href and part values until runtime using the pfx:href and pfx:part tags instead of the according attributes. The following example shows how to use values coming from the DOM result tree:

<pfx:include level="runtime">
  <pfx:href><ixsl:value-of select="/formresult/mypartinfo/@myhref"/></pfx:href>
  <pfx:part><ixsl:value-of select="/formresult/mypartinfo/@mypartname"/></pfx:part> 
</pfx:include>
[Note]Note
You should be aware that parts included at runtime aren't transformed with the master.xsl stylesheet, i.e. if the included part contains Pustefix tags (forms, buttons, etc.) they won't work. So the runtime inclusion is primarily intended for including simple text/HTML content.

5.3.2. Generated include requests (<pfx:maincontent>)

Looking at the example naturally leads to the question how it is possible to generate different pages with only a small number of structural xml files and always the same XSLT stylesheets. The answer is that at least one of the include parts isn't included via the pfx:include tag (which only handles static attribute values) but instead the filename of the include part is auto generated from the name of the page that is to be produced.

Looking at this page, one can see that the two transformations which produce BazPage.xml resp. BazPage.xsl have the page name supplied through the use of an XSLT transformation parameter. Using this parameter, the tag pfx:maincontent constructs an include request depending on the page name.

<pfx:maincontent part="content" path="MyProject/txt/pages" prefix="main_"/>

Table 5.4. Attributes of the pfx:maincontent tag

Attribute name Mandatory? Description
path optional If not given, but a XSLT parameter $maincontentpath has been defined in the depend.xml.in file, the value of the parameter is used. If there's even no $maincontentpath parameter, it defaults to PROJECTNAME/txt/pages
prefix optional defaults to main_
postfix optional defaults to .xml
part optional defaults to content
search optional Not set by default. Set this to dynamic if a matching part should be dynamically searched. The search order is: current project, common folder, and if a module is specified: overriding module, specified module. See Section 7.5, “Dynamic resource inclusion”.
module optional Not set by default. Set this to the name of the module from which the part should be loaded. If dynamic search is set, the module just serves as fallback location if the part can't be found in the project or common location and isn't overrided by another module. See Section 7.5, “Dynamic resource inclusion”. You can also use one of the pre-defined special module names (see Special module names).


For the page "home" this is equivalent to <pfx:include href="MyProject/txt/pages/main_home.xml" part="content"/> and of course similar for every other page.

Starting with this page specific include, the content of the page can be included from many different include parts.

5.3.3. Displaying images (<pfx:image>)

HTML <img> tags are usually not written directly, instead they are generated by using the <pfx:image> tag. Using this tag makes sure that the used image is registered in the runtime system as a dependency of the current target that's being generated.

One important feature of the <pfx:image> tag is that it inserts the natural height and width of the requested image unless they are explicitely given (as attributes width and height of course).

<pfx:image src="some/path/to/img.gif" themed-path="some/path" themed-img="img.gif"/>

Table 5.5. Attributes of the pfx:image tag

Attribute name Mandatory? Description
src optional

The src path references an image in the file system. It must be given as a path relative to the docroot (typically this means something like MyProject/img/foo.gif. Note that you need to define an alias for the image directories in your project.xml config file for Apache to be able to find the image.

Note that you can either specify the src attribute OR both of themed-path and themed-img

themed-path & themed-img optional

These two attributes allow for themed images. The mechanism uses the same theme fallback queue as it is used for include parts. The algorithm to find the image to use is quite easy:

Build an image path by concatenating themed-path, a / sign, the most specific theme, a / sign, and themed-img. Check if this image exists. If yes, use it as the src attribute for the resulting img tag. If not, take the next specific theme from the fallback queue (if it exists) and try again, until the image is found.

Example: The themes fallback list is "foo bar default", themed-path is "MyProject/img" and themed-img is "test.gif". The image file names that are tried one after the other are

  • MyProject/img/foo/test.gif
  • MyProject/img/bar/test.gif
  • MyProject/img/default/test.gif
level optional Not set by default. Set this to runtime if the image should be included at runtime (on the last transformation level).
search optional Not set by default. Set this to dynamic if the image should be dynamically searched. The search order is: current project, common folder, and if a module is specified: overriding module, specified module. The search ends if a matching image is found, regardless if there are matches with a more specific theme down the search chain. See Section 7.5, “Dynamic resource inclusion”.
module optional Not set by default. Set this to the name of the module from which the image should be loaded. If dynamic search is set, the module just serves as fallback location if an according image can't be found in the project or common location or another overriding module. See Section 7.5, “Dynamic resource inclusion”. You can also use one of the pre-defined special module names (see Special module names).
other attributes optional

All other attributes given (e.g. alt, width, height, title, etc. are copied unchanged into the resulting img tag


If you're including an image at runtime using level="runtime", you can also defer the creation of the src, alt, themed-path and themed-img values until runtime using the pfx:src, pfx:alt, pfx:themed-path and pfx:themed-img tags instead of the according attributes. The following example shows how to use values coming from the DOM result tree:

<pfx:image level="runtime">
  <pfx:src><ixsl:value-of select="/formresult/myimginfo/@mysrc"/></pfx:src>
  <pfx:alt><ixsl:value-of select="/formresult/myimginfo/@myalt"/></pfx:alt> 
</pfx:image>

5.3.4. Dynamically including parts at rendering time

Include parts can be also rendered standalone and included at runtime/rendering time on the last transformation level. Thus you can include parts dynamically, e.g. you can include a part based on information from the result tree, which isn't available on the former transformation levels. This can also help to keep your stylesheets small, but it also has some drawbacks: standalone rendering of a part costs more time than doing a regular include and the XML source context node can only be relative if the part is rendered within the main transformation, and not standalone, e.g. triggered by an AJAX call.

Render includes are done using the <pfx:render> tag. Its syntax is similar to <pfx:include>. But referenced parts additionally have to be marked as render parts using the render attribute. You can optionally specify in which variants a render part should be available. All this is necessary to know which parts can be invoked dynamically when pre-generating all pages for production environments. Setting the contextual attribute to true you can specify that a part is context-dependent, meaning that the initial context node for the transformation will be set to the current context node of the main transformation.

<include_parts>
  <part name="Foo" render="true" render-variants="foo foo:bar:baz" contextual="true">
    <theme name="default">
      ...
    </theme>
  </part>
</include_parts>

A part is referenced with at least a part attribute. The path to the include file can be omitted if you're referencing a part within the same file. Using module and/or search you can reference parts from modules or do dynamic lookups.

<pfx:render href="txt/common.xml" part="Foo"/>

Table 5.6. Attributes of the pfx:render tag

Attribute name Mandatory? Description
href optional If not set, it defaults to the current include part file.
part mandatory The name of the part to include
search optional Not set by default. Set this to dynamic if a matching part should be dynamically searched. See Section 7.5, “Dynamic resource inclusion”.
module optional Not set by default. Set this to the name of the module from which the part should be loaded. See Section 7.5, “Dynamic resource inclusion”.


Render include parts not just can be rendered as part of another page within the main transformation, but they can be also rendered standalone. Therefor Pustefix provides a Javascript API which lets you easily request a part via AJAX.

<script type="text/javascript" src="{$__contextpath}/modules/pustefix-core/script/httpRequest.js"></script>
<script type="text/javascript" src="{$__contextpath}/modules/pustefix-core/script/render.js"></script>

<script type="text/javascript">

   function callback(result) {
      document.getElementById("result").innerHTML = result;
   }

   function errorCallback(res) {
      if(res.status == 403) {
         window.location.reload();  
      } else {
	 //do something else
      }
   }

   function test() {
      // Remotely renders an include part and passes the output to a callback function.
      // Arguments can be passed as list (deprecated) or as single JSON object argument:
      //   href          - path to include file
      //   part          - include part name
      //   module        - module name (optional)
      //   search        - search type (optional)
      //   callback      - callback function (optional)
      //   context       - callback object scope (optional)
      //   requestId     - request/response assignment id (optional)
      //   params        - additional request parameters (optional)
      //   requestPath   - alternative request URL path (optional)
      //   errorCallback - callback function for HTTP errors (optional)
      // Deprecated version:
      //   pfx.render("txt/common.xml", "Foo", "", "", callback, null, "1", null, null, errorCallback);
      // Recommended version:
      var args = { "href": "txt/common.xml",
                   "part": "Foo", 
                   "callback": callback, 
                   "requestId": "1", 
		   "errorCallback": errorCallback };
      pfx.render(args);

      // you can alternatively set a global error function which is called
      // for all HTTP request errors in the abscence of a specific funtion:
      pfx.net.HTTPRequest.errorCallback = function(res) { alert(JSON.stringify(res)); };
   }
</script>

5.3.5. Checking include part existence (<pfx:checkinclude>)

The <pfx:checkinclude> and <pfx:checknoinclude> tags can be used to display content depending on the existence of an include part.

<pfx:checkinclude part="foo" href="txt/afile.xml">
  Display if part exists
</pfx:checkinclude>

<pfx:checknoinclude part="bar" href="txt/afile.xml">
  Display if part not exists
</pfx:checknoinclude

<pfx:checkinclude href="txt/afile.xml" module="amodule" search="dynamic" level="runtime">
  <pfx:part><ixsl:value-of select="/formresult/displaypart/@name"/></pfx:part>
  Display if part exists
</pfx:checkinclude>

Supported attributes and usage comply with the <pfx:include> element (see Section 5.3.1, “Include parts (<pfx:include>)”).

5.3.6. Include parameters (<pfx:includeparam>)

When including a part using <pfx:include>, you can pass parameters, which will be available within the templates applied during the current XSL transformation.

An include parameter can be passed by adding an according <pfx:includeparam> child element to the <pfx:include> element. The syntax is similar to XSL template parameters.

<pfx:include part="foo">
  <pfx:includeparam name="myparam">myvalue</pfx:includeparam>
  <pfx:includeparam name="another" select="/xpath/expression"/>
  <pfx:includeparam name="xmlparam" tunnel="yes">
     <item id="foo"/>
     <item id="bar"/>
  </pfx:include>
</pfx:include>

Include parameter values can be simple strings, nodesets or evaluation results of XPath expressions. Include parameters can be tunneled, i.e. you can pass parameters to nested includes without having to newly declare the parameter on each include level.

Within the included part you can access passed include parameters by declaring them at the top of the part. Like with XSL parameters, you can just declare empty parameters, set default values or mark a parameter as tunneled (without marking it, it will be overlaid).

<part name="hey">
  <theme name="default">
    <pfx:includeparam name="myparam"/>
    <pfx:includeparam name="another">default</pfx:include-param>
    <pfx:includeparam name="xmlparam" tunnel="yes"/>

Having declared an include parameter, you can use one of Pustefix's builtin templates to operate on this parameter, e.g. write its value, iterate over its value or check a condition against it. You can not only reference the parameter itself, but write complex XPath expressions.

<pfx:value-of select="$myparam"/>
<pfx:value-of select="concat($myparam,'xyz')"/>

<pfx:for-each select="$xmlparam/item">
  <pfx:value-of select="@id"/>
</pfx:for-each>

<pfx:if test="$another='...'">...</pfx:if>

<pfx:choose>
  <pfx:when test="$another='...'">...</pfx:when
  <pfx:otherwise>...</pfx:otherwise>
</pfx:choose>

Basically these tags are working similar to the according XSLT base tags, i.e. the templates matching these tags, are internally calling the suitable XSLT statements. But you have to be aware that there's a difference between applying templates on XML tags and directly executing XSLT statements. Here's the list of all supported tags:

  • pfx:value-of
  • pfx:copy-of
  • pfx:if
  • pfx:choose / pfx:when / pfx:otherwise
  • pfx:for-each / pfx:sort
  • pfx:element
  • pfx:attr

Besides these special templates some of Pustefix's core templates also have support for include parameters, i.e. the according elements provide attributes which support include parameters or XPath expressions. Usually these attributes are prefixed with select-.

<pfx:include select-href="$myparam" select-part="concat($anotherparam,'xyz')"/>

<pfx:checkinclude select-part="$myparam" select-href="..."/> 

<pfx:checknoinclude select-part="$myparam" select-href="..."/> 

<pfx:image select-src="$myparam" select-alt="..."/>

5.4. Displaying messages

Pustefix can display messages coming from a Spring MessageSource. It's an alternative to outputting text using include parts as described in Section 5.3.1, “Include parts (<pfx:include>)”, especially suitable if you want to use already exisiting internationalization mechanisms, like resource bundles or gettext PO files, or if you need to access the messages from within the Java logic.

5.4.1. Message tags

Messages can be displayed using the <pfx:message> tag. You need to specify a key referencing the message ID (or property key in terms of property files).

<pfx:message key="my.message.key"/>

Messages can have arguments which are passed using <pfx:arg> tags. Pustefix automatically fills them in at the specified position (denoted by the message string placeholders {0}, {1}, etc.).

<pfx:message key="my.message.key">
   <pfx:arg>my argument</pfx:arg>
   <pfx:arg>another argument</pfx:arg>
</pfx:message>

If the message string contains markup or you're passing markup as argument, you have to set the attribute disable-output-escaping to yes, if you want to render the markup without escaping it:

<pfx:message key="my.message.key" disable-output-escaping="yes">
   <pfx:arg><pfx:button page="test">testlink</pfx:button></pfx:arg>
</pfx:message>

Besides the message arguments, the message key itself can be set at runtime, e.g. for dynamically including messages based on values from the DOM tree:

<pfx:message>
   <pfx:key><ixsl:value-of select="/formresult/path/to/@messageid"/></pfx:key>
</pfx:message>

Using the <pfx:checkmessage> and <pfx:checknomessage> tags you can check if a message is existing or not:

<pfx:checkmessage key="my.message.key">
   <!-- Apply if message exists -->
</pfx:checkmessage>
<pfx:checknomessage key="my.message.key">
   <!-- Apply if message not exists -->
</pfx:checknomessage>
<pfx:checkmessage key="my.message.key">
   <pfx:checkpassed>
      <!-- Apply if message exists -->
   </pfx:checkpassed
   <pfx:checkfailed>
      <!-- Apply if message not exists -->
   </pfx:checkfailed>
</pfx:checkmessage>

5.4.2. MessageSource configuration

The MessageSource can either be configured using Spring means, or, as recommended, within the Pustefix project configuration (which makes it available during pre-generation of the pages too).

You can specify one or more MessageSources (the latter ones will serve as parent MessageSource for the former ones). For each MessageSource you have to specify its type (po or properties) and one or more basenames.

<messagesources>
   <!-- MessageSources can be preprocessed by doing regular expression based search and replace on its messages.
        The regular expression pattern follows the syntax of java.util.regex.Pattern.
   <preprocess>
      <replace pattern="\{MY_PLACEHOLDER\}" value="MY_REPLACEMENT"/>
   </preprocess> -->
   <messagesource type="po">
      <basename>module://common-module/i18n/messages</basename>
      <basename>module://common-module/i18n/errors</basename>
   </messagesource>
   <messagesource type="properties" basename="/WEB-INF/messages"/>
</messagesources>

According to the specified type and selected locale Pustefix will look up the messages in all matching files, e.g. for the locale en_UK starting with messages_en_UK.po, messages_en.po and messages.po, whereas messages in previous files (previous basename or MessageSources) override later ones.

5.4.3. Message format

Messages in property or po files should conform to the Java java.text.MessageFormat standard, see the following examples:

#Sample property file
my.message.key=My message text
another.key=Message text with arguments {0} and {1}
#Sample po file
msgid "my.message.key"
msgstr "My message text"
msgid "another.key"
msgstr "Message text with arguments {0} and {1}"

5.5. Handling HTML forms

Pustefix supplies a complete set of tags that replace the standard HTML form element tags. The advantage of using these tags is that they are automatically pre-filled with values delivered from the backend.

5.5.1. Form creation

The creation of a html form is done with the help of the tag <pfx:forminput>. Most often you don't need to set any attributes, the defaults should work just fine.

<pfx:forminput send-to-page="APage" send-to-pageflow="APageFlow" frame="AFrameName" target="ATargetName" type="auth|data">
  <!-- place form elements here -->
</pfx:forminput>

Table 5.7. Attributes of the pfx:forminput tag

Attribute name Mandatory? Description
type optional

Defaults to data. Must be set to auth, if the submitted information contains authentication data (e.g. userid, password).

target optional

Defaults to the parent frame (if frames are used, current window otherwise)

frame optional

This is used to select which named frame is about to be loaded into the target frame after submit. The default when frames are used is the parent frame.

Most often you have to set neither frame nor target.

send-to-page optional

Selects the page the submitted data is send to. Default is to use the current page. Most of the time, this is the right thing to do.

send-to-pageflow optional

Should not be used. Selects the pageflow to use. Default is to not set a pageflow name explicitely, but let the backend reuse the current flow or select a matching one. Leave it that way if you don't know why you want to change it. If you want to select a pageflow to use, better do so via the submit button as explained below. Note that this mechanism will most likely not work when using the send-to-pageflow attribute here.


Pustefix usually by default generates some hidden fields which are placed directly under the HTML form tag, but you can explicitely control where Pustefix should place the fields using the <pfx:hiddenfields/> element.

<pfx:forminput>
  <div>
    <pfx:hiddenfields/>
  </div>
</pfx:forminput>

5.5.2. Submitting forms

Make sure that all the other form related tags detailed below are contained inside a pfx:forminput block.

The form can be submitted by clicking on submit controls which can be realized in two ways: The simple html submit button is made with the tag <pfx:xinp type="submit">, while using an image as the submit control is done with <pfx:xinp type="image">.

A very useful ability of both submit controls is that it's possible to attach additional form parameters to them, that are only transmitted when that exact submit control is pressed. This way it's possible for a form to have two or more submit controls, each with different additional data attached and submitted, depending on which submit control the user clicks on.

<pfx:xinp type="submit|image" pageflow="APageFlow" jumptopage="APage" jumptopageflow="APageFlow" forcestop="true|false|step">
  <!--
    Attach additional information to the controls
  -->
  <pfx:command page="APage" name="SUBWRP">prefix</pfx:command>
  <pfx:argument name="AName">AValue</pfx:argument>
  <pfx:anchor frame="AFrame">AnAnchor</pfx:anchor>
</pfx:xinp>

Table 5.8. Attributes of form submit controls

Attribute name Mandatory? Description
type mandatory

submit creates a simple html submit button, image uses an image as the submit button.

pageflow optional

Used to explicitely set a pagflow to use after the submit has been handled sucessfully. Note that there is no corresponding way to set the page the submit is directed at, you need to set the send-to-page attribute of the pfx:forminput tag.

jumptopage / jumptopageflow optional

If you don't want the pageflow mechanism to control which page to display as the next page after a successful submit, you can set this page via the jumptopage attribute. In this case, you also have the possibility to set the pageflow to use for this follow-up page and submit circle via the jumptopageflow attribute.

forcestop optional

Default is false. If you don't want the pageflow mechanism control wether to stay on the current page after a successful submit or not, you can unconditionally force it to stay on the current page (when setting the attribute to true). Alternatively, if you want the pageflow mechanism to stop at most no more than one step further in the flow (e.g. a wizard like application) you can set this attribute to step.


When setting the type to image, you may also specify the attributes src, themed-path, themed-img, search and module. These attributes work exactly as described in Section 5.3.3, “Displaying images (<pfx:image>)”.

You can alternatively use the pfx:submitbutton tag to produce a button element instead of an input element. The button elements are more flexible than the traditional input elements, but are not supported by some old browsers.

<pfx:submitbutton pageflow="APageFlow" jumptopage="APage" jumptopageflow="APageFlow" forcestop="true|false|step">
      <!-- you can put additional stuff here as described above, but in contrast to the traditional
           submit element you can also have content (like text or images) here -->
</pfx:submitbutton>

5.5.3. Arguments, comands and anchors

Command controls (like described in Section 5.5.2, “Submitting forms”) and links (like described in Section 5.2.1, “pfx:button”) can contain child tags that are used to pass additional data when the link or button is clicked.

Anchors

The <pfx:anchor> tag is used to supply an anchor for a link or submit button.

<pfx:anchor frame="AFrame">AnAnchor</pfx:anchor>

The content gives the name of the anchor as it's defined in the target page.

Table 5.9. Attributes of pfx:anchor

Attribute name Mandatory? Description
frame optional

You need to set it if the page uses frames or not. In this case you can use more than on pfx:anchor tag, one for each frame you want to define an anchor for. This way it's possible to scroll each frame independently to the desired position with one request (without the need for JavaScript). If you don't use frames, only one pfx:anchor tag makes sense, without a frame attribute.


Arguments

The <pfx:argument> tag is used to supply additional parameters for the request.

<pfx:argument name="AName">AValue</pfx:argument>

The content gives the value of the parameter to submit. It's also possible that this value is only known at runtime by using values dynamically supplied from the DOM tree. This may be handled like here:

<pfx:argument name="foo"><ixsl:value-of select="/formresult/bar"/><pfx:argument>

Table 5.10. Attributes of pfx:argument

Attribute name Mandatory? Description
name mandatory

The name of the argument to submit.


Commands

The <pfx:command> tag can be used to explicitly select the wrappers on a page, which should be used for handling the submitted data or retrieving the updated data after a successful submit.

 <pfx:command page="APage" name="SUBWRP|RETWRP">prefix</pfx:command>

The content gives the prefix of the wrapper to select as defined on the target page.

[Note]Note

If you don't give any selected wrappers for submit handling (SUBWRP), all wrappers defined on the target page become selected for submit handling.

If you don't give any selected wrappers for retrieving data after successful submits (RETWRP), no wrappers are selected for retrieving data.

It's recommended to use actions instead of commands. Actions let you define the selected wrappers within the configuration, which is a much cleaner approach.

Table 5.11. Attributes of pfx:command

Attribute name Mandatory? Description
name mandatory

The name of the command. Currently, the supported commands are SUBWRP, RETWRP and the deprecated SELWRP, which is an equivalent of SUBWRP and only supported for backwards compatibility.

All commands select a wrapper with the matching prefix on the page that is designated by the page attribute (or the current page, when empty).

SUBWRP selects a wrapper that should be used for handling the data that is submitted.

RETWRP selects a wrapper whose data should be retrieved/updated after a successfull submit.

You can use more than one pfx:command to select as many wrappers as you want.

Note: As the recommended way to select wrappers for submitting/retrieving data is the action mechanism, it is possible that the whole pfx:command stuff will go away in future releases.

page optional

Most often empty. This should be set to the page the request is directed at if it's not the current page.


5.5.4. Form elements

Using these form element tags ensures that values, that are supplied by the backend application are used as values for the generated html form elements. This is done dynamically by generating the necessary ixsl: statements that will check the DOM tree for matching values and adding them as value attributes (text, password and hidden input fields) or content (text areas) or selecting them according to their value (check boxes, radio boxes, option menus).

Another thing these tags do is to automatically check if the runtime DOM tree contains an error attached to their name. In this case, the resulting html input field is augmented with special CSS classes to help with styling these fields depending on the state they have (error/no error).

Typically the CSS used when no attached error is detected is just the value of a class attribute given to the pustefix input tag. On the other hand, if an error is attached, the class attribute looks like this:

[@class] PfxError PfxInputTextError [PfxErrorLevel_$level]

where @class is the user supplied class attribute mentioned above (if it's there at all) and $level is an optional attribute of the error element in the runtime DOM tree. The example works the same for other input fields of the form <input type="...">, by replacing "Text" with "Password", "Check" or "Radio" (Hidden input fields are not visible anyway. Select menus and text areas don't need a special class as they can be easily selected via CSS rules. For the last two, the error CSS looks like this:

[@class] PfxError [PfxErrorLevel_$level])

Creating a text input field

Text input fields are created using the <pfx:xinp> tag.

<pfx:xinp type="text" name="AName" default="AValue" position="1|..|n" class="ACssClass">
  <!--
    name and default can also be set at runtime
  -->
  <pfx:default>
    <ixsl:value-of select="/some/runtime/value"/>
  </pfx:default>
  <pfx:name>
    <ixsl:value-of select="/some/runtime/name"/>
  </pfx:name>
 </pfx:xinp>

Table 5.12. Attributes of pfx:xinp[@type="text"]

Attribute name Mandatory? Description
name or <pfx:name> mandatory

The name of the parameter. Use the name attribute when the name is a known literal value. If it must be computed at runtime (e.g. from the runtime DOM tree), you can use the <pfx:name> child element. In the end, the form of a parameter name should be prefix.InternalName.

name or <pfx:default> optional

The difference between the attribute and the child element form is analog to the description given above. You can use the default specification to pre-set a value for the case that the backend doesn't set one on it's own.

position optional

Default is 1. Useful only for parameters which are defined to occur multiple times, i.e. the same parameter name may appear more than one time in the output from the backend system to pre-fill elements on the UI. Wiht position you can decide, which of those multiple outputs under the same name you want to reference for the pre-filled value.

class optional

See Section 5.5.4, “Form elements” for an explanation on how the CSS class(es) for the html element are constructed.


Creating a text area field

Text area fields are created the same way as text input fields (see the section called “Creating a text input field”), you only have to set the type attribute to area.

Basically the same attributes as for <pfx:xinp type="text">, with the exception that there's no default attribute. The same way as it works for the html textarea tag, the content of the element (minus the special <pfx:name> child) becomes the default value for the form input element.

If you plan to mix the content with additional non-content markup, you should put the default content into a <pfx:default> element. Thus the non-content markup is copied through, even if the default content isn't applied because of existing content from the DOM tree.

Creating a password field

Password fields are created the same way as text input fields (see the section called “Creating a text input field”), you only have to set the type attribute to password.

Similar to <pfx:xinp type="text">, but without the ability to set a default value from the UI and the position is fixed to be "1".

Creating a hidden field

Hidden fields are created the same way as text input fields (see the section called “Creating a text input field”), you only have to set the type attribute to hidden.

Basically the same attributes as for <pfx:xinp type="text">. Of course no special class attribute handling, as the result is invisible anyway.

Creating a radio or check box

Radio buttons and checkboxes are created using the <pfx:xinp> tag as well.

<pfx:xinp type="radio|check" name="AName" value="AValue" default="true|false" class="ACssClass">
  <pfx:name>
    <ixsl:value-of select="/some/runtime/name"/>
  </pfx:name>
  <pfx:value>
    <ixsl:value-of select="/some/runtime/value"/>
  </pfx:value>
  <pfx:default>
    <ixsl:value-of select="/runtime/true/or/false"/>
  </pfx:default>
</pfx:xinp>

Table 5.13. Attributes of pfx:xinp[@type="radio|check"]

Attribute name Mandatory? Description
name or <pfx:name> mandatory

The name of the parameter. Use the name attribute when the name is a known literal value. If it must be computed at runtime (e.g. from the runtime DOM tree), you can use the <pfx:name> child element. In the end, the form of a parameter name should be prefix.InternalName.

A group of radio- or checkboxes share the same name and differ by the value they submit when they are checked.

value or <pfx:value> mandatory

Needed to set the value that is to be submitted when the element is checked. The difference between the attribute and the child element form is analog to the description given above.

default or <pfx:default> optional

Allowed values are true and false. The difference between the attribute and the child element form is analog to the description given above. You can use the default specification to decide if the form element is checked for the case that the backend doesn't supply which of the group's members are to be checked.

class optional

See Section 5.5.4, “Form elements” for an explanation on how the CSS class(es) for the html element are constructed.


Creating option menus

Radio buttons and checkboxes are created using the <pfx:xinp> and <pfx:option> tags.

<pfx:xinp type="select" name="AName" class="ACssClass" multiple="true|false">
  <pfx:name>
    <ixsl:value-of select="/some/runtime/name"/>
  </pfx:name>

  <!--
    One option tag per option that is available in the menu
  -->
  <pfx:option value="AValue" default="true|false">
    <pfx:value>
      <ixsl:value-of select="/some/runtime/value"/>
    </pfx:value>
    <pfx:default>
      <ixsl:value-of select="/runtime/true/or/false"/>
    </pfx:default>
  </pfx:option>
</pfx:xinp>

Table 5.14. Attributes of pfx:xinp[@type="select"]

Attribute name Mandatory? Description
name or <pfx:name> mandatory

The name of the parameter. Use the name attribute when the name is a known literal value. If it must be computed at runtime (e.g. from the runtime DOM tree), you can use the <pfx:name> child element. In the end, the form of a parameter name should be prefix.InternalName.

class optional

See Section 5.5.4, “Form elements” for an explanation on how the CSS class(es) for the html element are constructed.

multiple optional

Defaults to false. Set to true when you want to have a select menu with multiple selectable options.


Table 5.15. Attributes of pfx:option

Attribute name Mandatory? Description
value or <pfx:value> mandatory

Needed to set the value that is to be submitted when the element is checked. The difference between the attribute and the child element form is analog to the description given above.

default or <pfx:default> optional

See Section 5.5.4, “Form elements” for an explanation on how the CSS class(es) for the html element are constructed.

multiple optional

Allowed values are true and false. The difference between the attribute and the child element form is analog to the description given above. You can use the default specification to decide if the option is selected for the case that the backend doesn't supply which of the options are to be selected.


All other attributes are copied to the resulting HTML element.

5.5.5. Handling error conditions

Errors in Pustefix come in two variants:

  1. Field errors that are attached to a form input field and which must be handled on the page the form element is defined on.

  2. Page messages which are (not neccessary fatal) errors or warnings which may or may not be displayed on the current or the following page.

    Page messages are not associated to a single form element, but are used for general feedback on the status of the application.

Errors attached to a field

The <pfx:checkerror> tag is used to display content depending on the fact if a field error (or possibly a field error with a special level, prefix or name attribute) is present in the runtime DOM tree. Optionally you can restrict displaying on the request trigger type (direct, flow or submit). There's also a tag with the inverted semantics, <pfx:checknoerror>, displaying content if the conditions above don't apply:

<pfx:checkerror level="ALevel" prefix="APrefix" name="APrefixedName" trigger="direct|flow|submit">
  <!--
    Content to be displayed when any error with the matching level, prefix or name attribute is present in the
    runtime DOM tree. All attributes are optional, without restricting attributes, any error will trigger
    the display of the content of the pfx:checkerror tag. If a trigger attribute is set, the content will be only
    displayed if the trigger causing the DOM creation matches the attribute value.
  -->
</pfx:checkerror>

<pfx:checknoerror level="ALevel" prefix="APrefix" name="APrefixedName" trigger="direct|flow|submit">
  <!-- 
    Inverted semantics as pfx:checkerror: content is displayed if no errors (matching the optional conditions) are found.
  -->
</pfx:checknoerror>

The <pfx:checkfield> tag is used to display error messages depending on the form field they are attached to.

<pfx:checkfield name="prefix.Name">
  <pfx:error>
    <!--
      Content that's displayed when an error associated to the form field referenced
      in the name attribute is present in the runtime DOM tree.
    -->
  </pfx:error>
  <pfx:normal>
    <!--
      Content that's displayed when no error associated to the form field referenced
      in the name attribute is present in the runtime DOM tree.
    -->
  </pfx:normal>
  <!--
    Content outside of the two child nodes is displayed in both cases.
  -->
</pfx:checkfield>

There can be more than one of <pfx:error> and <pfx:normal> child nodes. These can be used to display completely different content based on the presence or not of an error.

The <pfx:checkfield> tag defines a set of ixsl variables that can be used inside its body. These are

  • $pfx_scode: The StatusCode node that represents the error. This can be used to call ixsl:apply-templates on to display the error message.
  • $pfx_level: The level attribute of the error in the runtime DOM tree (or empty, if there's no level attribute).
  • $pfx_class: The CSS class that has been constructed depending on the presence of the error. If there's no error, this variable is empty. Else, if the error is present and has no level attribute set, the value is just PfxError. If the error has a level attribute, the class is set to be PfxError PfxErrorLevel_{$pfx_level}.

Example 5.1. Using <pfx:checkfield>

<tr>
  <pfx:checkfield name="addr.Street">
    <td class="{$pfx_class}">Street:</td>
  </pfx:checkfield>
  <td><pfx:xinp type="text" name="addr.Street"/></td>
</tr> 
<pfx:checkfield name="addr.Street">
  <pfx:error>
    <tr>
      <td colspan="2" class="{$pfx_class}">
        <ixsl:apply-templates select="$pfx_scode"/>
      </td>
    </tr>
  </pfx:error>
</pfx:checkfield>

You can see how the second <pfx:checkfield> inserts a whole new row in the table structure when and only when a matching error happens. Also note how the defined $pfx_class variable is used to style content depending on the presence of an error.


Checking for pagemessages

The <pfx:checkmessage> tag is used to display content depending on the fact if a page message (or possibly a page message with a special level attribute) is present in the runtime DOM tree.

<pfx:checkmessage level="AString">
  <!--
    Content to be displayed when any page message with the matching level attribute is
    present in the runtime DOM tree. The level attribute is optional, when it's missing,
    any page message will trigger the display of the content of the pfx:checkmessage tag.
  -->
<pfx:checkmessage>

The <pfx:messageloop> tag can be used to repeat content for every page message that is present in the runtime DOM tree.

<pfx:checkmessage level="AString">
  <pfx:messageloop>
    <!--
      This content is repeated for all the page messages that match the restrictions imposed by
      the parent's level attribute (or all page messages, if the attribute isn not given).
    -->
  </pfx:messageloop>.
</pfx:checkmessage>

The <pfx:messageloop> tag defines a set of ixsl variables that can be used inside its body. These are

  • $pfx_scode: The StatusCode node that represents the error. This can be used to call ixsl:apply-templates on to display the error message.
  • $pfx_level: The level attribute of the error in the runtime DOM tree (or empty, if there's no level attribute).
  • $pfx_class: The CSS class that has been constructed depending on the presence of the error. If there's no error, this variable is empty. Else, if the error is present and has no level attribute set, the value is just PfxError. If the error has a level attribute, the class is set to be PfxError PfxErrorLevel_{$pfx_level}.

5.5.6. Avoiding duplicate form submission

You may face situations where you want to prevent, that the same form is submitted multiple times (e.g. by double-click, back button) or that a form, opened in a new window/tab, but already opened in another window/tab, can still be submitted from the old window/tab.

Using the <pfx:token> tag a hidden field with a random token is included into its parent form. The token is stored in the Context, keyed by a customizable name (by default pagename#elementid, which can be overwritten/replaced using the name attribute). After the form is submitted, the token is invalidated and submitting the form again causes the setting of a page message. Via the errorpage attribute you can optionally jump to an error page.

<pfx:forminput>
  <pfx:token errorpage="multisubmiterror"/>
  <!-- place form elements here -->
</pfx:forminput>

You should be aware that this mechanism depends on the caching behaviour of the used browser, e.g. for browsers, which don't cache but reload pages accessed via the back button, it can't prevent the repeated form submission, because the form in fact is a new instance and we can't detect or decide that the submit is illegal.

This mechanism by default only prevents forms with illegal tokens from being processed. If you want to ensure that form submits including no token will fail too, you can force this behaviour by setting the requirestoken attribute at the pagerequest's input element to true. This will force tokens for every form that's submitted to this page.

<contextxmlserver>
  <!--
    servlet config options
  -->
  <pagerequest name="...">
    <input requirestoken="true">
      <interface prefix="..." class="..."/>
    </input>
  </pagerequest>
</contextxmlserver>

5.5.7. Preventing CSRF attacks

The best solution against CSRF attacks is to allow requests which change data only when they use the POST method together with sending a secure random synchronizer token (bound to the user's session).

This can be enforced in the according pagerequest configuration by setting the protected attribute at the <input> element to true (see Section 4.3.5, “ContextXMLService configuration file”). The token will be automatically added as hidden input parameter if you're using the <pfx:forminput> element, alternatively you can use the tag <pfx:csrftoken/> to manually insert the element.

If Pustefix detects a protection violation, the submit will be cancelled, a log entry will be written (LOGGER_CSRF), an according page message will be set and the page will be re-displayed.

5.6. Miscellaneous utility tags

Pustefix also provides several utility tags, that might be helpful in your application.

5.6.1. Checking page status

The <pfx:checkactive> and <pfx:checknotactive> allow you to check from the XSL-stylesheet, whether a specific IHandler currently is active or not.

<pfx:checkactive page="APageName" prefix="AHandlerName">
  <!--
    Content to be displayed, if the handler is active
  -->
</pfx:checkactive>

Table 5.16. Attributes of the pfx:checkactive and pfx:checknotactive tags

Attribute name Mandatory? Description
page optional

When using the page attribute, the content of the tag is only displayed if the referenced page is accessible. This is an easy way to display complete subparts of a page depending on the accessibility of another page.

prefix optional

When using the prefix, the content of the tag is only displayed if a referenced handler on the current page is active. The prefix is the same name as used in the servlet property file for the handler.

You can only use one of the attributes page and prefix.


5.6.2. Displaying content based on the language

The selection mechanism of <pfx:langselect> allows to select matching content anywhere inside a include part depending on the current language that is set in the running session. The <pfx:lang name="default"> tag is acting as the fallback for all languages that don't have a better, more specific named <pfx:lang> node.

<pfx:langselect>
  <pfx:lang name="en_*">...</pfx:lang>
  <pfx:lang name="en_GB">...</pfx:lang>
  <pfx:lang name="default">...</pfx:lang>
</pfx:langselect>

The system doesn't enforce but expects languages to be of the standard form of ISO language codes. In this case, a <pfx:lang> node with a name attribute ending in an asterisk (*) can be used to create a fallback for a whole "family" of languages. In the example above, the en_* node will serve as a fallback for all language codes starting with en_ except en_GB (which has a more specific node below).

5.6.3. Displaying content based on the theme

The selection mechanism of <pfx:themeselect> allows to select matching content anywhere inside a include part depending on the same selection mechanism that is used to select the include part the first hand as described in Section 5.3.1, “Include parts (<pfx:include>)”

<pfx:themeselect>
  <pfx:theme name="ATheme">...</pfx:theme>
  <pfx:theme name="default">...</pfx:theme>
</pfx:themeselect>

Of course this makes most sense, when the containing include part uses a general theme, so there are specialized themes in the theme fallback queues to select from.

[Note]Note

This tag is only to be used in very special situations. Normally you would like to use different themes for the include part to register itself correctly with the runtime system.

5.6.4. Using the Pustefix console

Pustefix provides an edit-console and webservice-console that are helpful during debugging.

Those consoles can be included in your pages using the <pfx:editconsole> and <pfx:webserviceconsole> tags. The webservice-console may also be included within the editconsole using the following form: <pfx:editconsole webserviceconsole="true">.

5.7. XSLT extensions

5.7.1. XPath functions

In addition to the standard XPath functions supplied by the XSLT/XPath implementation Pustefix provides some additional framework specific XPath functions in the xmlns:pfx="http://www.schlund.de/pustefix/core" namespace:

Example:

<ixsl:if test="pfx:isVisible('mypage')=1">
  Display if page 'mypage' is accessible 
</ixsl:test>

Page functions

Function: number pfx:isVisible(string)

The pfx:isVisible function checks if a page is accessible. The page name is passed as argument. The function returns:

  • 1, if the page is accessible
  • 0, if the page isn't accessible
  • -1, if the page doesn't exist

Function: number pfx:isVisited(string)

The pfx:isVisited function checks if a page has already been visited within the session. The page name is passed as argument. The function returns:

  • 1, if the page has already been visited
  • 0, if the page hasn't been visited yet
  • -1, if the page isn't configured

[Note]Note

You should be aware that doing a boolean check on results of type number, 1 and -1 both are evaluated as true, 0 as false.

Function: string pfx:getDisplayPageName()

The pfx:getDisplayPageName function returns the display name of the current page.

Include functions

Function: boolean pfx:checkInclude(string, string, string, string)

The pfx:checkInclude function checks if an include part exists. The function expects four string arguments: href, part, module, search (same semantics as Section 5.3.5, “Checking include part existence (<pfx:checkinclude>)”). The function returns:

  • true, if the include part can be found
  • false, if the include part can't be found

Function: node-set pfx:getIncludeInfo(string, string, string)

The pfx:getIncludeInfo function returns information about the include parts contained in a resource or a set of resources (when doing a dynamic search). The function expects three string arguments: href, module (optional) and search (optional). The function returns a node-set with each node describing a part, e.g. its name.

Example:

<!-- iterates over the names of all parts from a file and includes the parts -->
<pfx:for-each select="pfx:getIncludeInfo('path/to/file.xml','amodule')/part">
   <pfx:include select-part="@name" href="path/to/file.xml" module="amodule"/>
</pfx:for-each>

String functions

Function: string pfx:encode-for-uri(string)

The pfx:encode-for-uri encodes reserved characters in a string for usage in the path segment of an URI. The function basically works like the according XPath 2.0 function (see http://www.w3.org/TR/xpath-functions/#func-encode-for-uri).

Function: boolean pfx:ends-with(string, string)

The pfx:ends-with function returns true if the first argument string ends with the second argument string, and otherwise returns false.

Function: string pfx:lower-case(string)

The pfx:lower-case function converts all of the characters in the arguments string to lower case.

Function: boolean pfx:matches(string, string, string?)

The pfx:matches function returns true if the text passed as first argument is matched by the pattern passed as second argument. You can optionally specify one or more pattern mode flags as third argument ('s' dot-all mode, 'm' multi-line mode, 'i' case-insensitive mode, 'x' pattern-whitespace-ignoring mode). The function basically works like the according XPath 2.0 function (see http://www.w3.org/TR/xpath-functions/#func-matches).

Function: string pfx:replace(string, string, string, string?)

The pfx:replace function replaces all substrings of the text passed as first argument, which match the pattern passed a second argument, with the replacement passed as third argument. You can optionally specify one or more pattern mode flags as third argument ('s' dot-all mode, 'm' multi-line mode, 'i' case-insensitive mode, 'x' pattern-whitespace-ignoring mode). The function basically works like the according XPath 2.0 function (see http://www.w3.org/TR/xpath-functions/#func-replace).

Function: string pfx:string-length(string)

The pfx:string-length returns the length of the passed string. The function basically works like the according XPath 2.0 function (see http://www.w3.org/TR/xpath-functions/#string-length).

Function: string pfx:substring(string, double, double?)

The pfx:substring function returns the portion of the the string passed as first argument, beginning at the position passed as second argument (first character is located at position 1!), optionally limited to the number of characters passed as third argument. The function basically works like the according XPath 2.0 function (see http://www.w3.org/TR/xpath-functions/#substring).

Function: node-set pfx:tokenize(string, string)

The pfx:tokenize function splits the first argument string into substrings by using the second argument string as separator (in the form of a regular expression).

Function: string pfx:upper-case(string)

The pfx:upper-case function converts all of the characters in the arguments string to upper case.

Date functions

Function: string pfx:format-date(string, string?, string?, string?)

The pfx:format-date function formats a timestamp (UNIX time in milliseconds) passed as first argument, using the date format pattern, timezone and locale, which can be optionally passed as further arguments.

<ixsl:value-of select="pfx:format-date(/formresult/path/to/@time)"/>
<ixsl:value-of select="pfx:format-date(/formresult/path/to/@time, '', 'Europe/Berlin')"/>
<ixsl:value-of select="pfx:format-date(/formresult/path/to/@time, 'EEE, d MMM yyyy HH:mm:ss Z', 'Europe/Berlin', 'de-DE')"/>

You can set sensible default values for the optional arguments by adding them as top-level XSL parameters:

<xsl:param name="date-format">EEE, d MMM yyyy HH:mm:ss Z</xsl:param>
<xsl:param name="date-timezone">Europe/Berlin</xsl:param>
<xsl:param name="date-locale">de-DE</xsl:param>

The syntax of all arguments is conforming to the Java standard, see the class java.text.SimpleDateFormat for reference.

Development functions

Function: void pfx:sleep(number)

The pfx:sleep function pauses XSLT processing for the specified number of milliseconds. This function is intended for debugging purpose only.

Miscellaneous functions

Function: string pfx:getEnvProperty(string)

The pfx:getEnvProperty function returns then environment property denoted by the passed argument.

Function: string pfx:escapeJS(string)

The pfx:escapeJS function escapes Javascript data by replacing non-alphanumeric characters by the according \xHH and \uHHHH escape sequences.

Function: boolean pfx:isBot()

The pfx:isBot function returns true if the current request is made by a bot, otherwise it returns false.

Function: Object pfx:getBean(string)

The pfx:getBean function gets a bean reference from the Spring ApplicationContext looking it up by its name or type. Thus you can call extension functions on instance level by passing the bean as first method argument, e.g.:

<ixsl:value-of xmlns:my="java:mypackage.MyClass" select="my:myMethod(pfx:getBean('myname'),'arg1','arg2')"/>

5.7.2. XSL extension elements

In addition to the standard XSL elements Pustefix provides some useful XSL extension elements. They can be used by declaring the namespace http://pustefixframework.org/org.pustefixframework.xslt.ExtensionElements, using the prefix pxsl and adding the prefix to the extension-element-prefixes attribute of the stylesheet:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:pxsl="http://pustefixframework.org/org.pustefixframework.xslt.ExtensionElements"
                extension-element-prefixes="pxsl" version="1.1">

There are some extension elements providing extended logging functionality. They work similar to the <xsl:message/> instruction, but additionally support different log levels and can optionally write to a Log4J logger instead of standard output.

<pxsl:log level="DEBUG|INFO|WARN|ERROR"
              logger="SOME_LOG4J_LOGGER"> 
  <!-- content -->
</pxsl:log>
    
<!-- shortcuts for specific log levels -->
<pxsl:debug> <!-- content --> </pxsl:debug>
<pxsl:info> <!-- content --> </pxsl:info>
<pxsl:warn> <!-- content --> </pxsl:warn>
<pxsl:error> <!-- content --> </pxsl:error>

If you don't specify a logger, the content will be written to standard output. But the log levels are bound to the application mode by default, i.e. the log messages will be filtered. Here's which messages are displayed in which mode:

  • prod => WARN, ERROR
  • test or devel => DEBUG, INFO, WARN, ERROR
  • other modes => INFO, WARN, ERROR

The <pxsl:fail-safe/> extension element catches an exception thrown during the processing of its child elements and outputs its stacktrace instead of the content. This element shouldn't be used on normal pages. It's intended for tooling and debugging purposes only.

<xsl:variable name="result">
   <pxsl:fail-safe>
      <xsl:value-of select="call:someExtensionFunction()"/>
   </pxsl:fail-safe>
</xsl:variable>

Chapter 6. Important Concepts

6.1. HTTP request handling

In a typical Pustefix application the DispatcherServlet provided by Spring is usually the only servlet. This servlet instantiates a Spring ApplicationContext on startup and delegates all requests to the HttpRequestHandler found by using the HandlerMapping. If you want to do request processing for a certain path without using the HttpRequestHandlers provided by Pustefix, you can add your own instance of an UriProvidingHttpRequestHandler to the Spring configuration file.

All request handlers used by Pustefix are based on AbstractPustefixRequestHandler. The main feature of it's session handling is that it's mostly transparent to the user. You can submit data to a URL, and the system will take care to store the supplied data (this is done in the form of a PfixServletRequest, which can be seen as basically the same as a HttpServletRequest, with some additional features, e.g. more or less transparent handling of file uploads), create a session, redirect to the URL with an embedded session id, and continue with the business logic and the data submitted with the original request afterwards.

Even SSL is handled transparently. A descending request handler must implement the method + needsSession(): boolean to decide if the the current request should result into the session to be "secure". You don't have to make sure that the webpages use "https://" in all needed links, the target itself tells the system that it want's to run under SSL.

If the request handler decides that it want's to run under SSL from now on, a complex redirect sequence happens that makes sure that all data is copied from the previous insecure session into a new, secure session (a session is secure if it's session id was never transmitted over an insecure channel). After a session has once transformed into a secure one, there's no way back: Every time you try to use the session with "http://" it will create a new, secure session dropping the old one (because it's tainted now). If the visitor's browser supports cookies, the system manages to map request with the old, original, insecure session id (which can happen whenever the user uses the back button of her browser to go back to a page that has still the old session id embedded in every link) to the new, currently running secure session and silenty and securely redirects to this session.

6.1.1. HttpRequestHandlers provided by the core framework

Figure 6.1, “Pustefix HTTP request handlers” shows some of the HttpRequestHandlers that are already provided by the core framework and should be sufficient for most of your needs when building a web application with Pustefix.

Figure 6.1. Pustefix HTTP request handlers

Pustefix HTTP request handlers

DerefRequestHandler

This is a small request handler that should be used whenever a link to a URL outside the own site is made. All such external links must be of the form /deref?link=http://some.other.domain/foo/bar. This is important because for any link that goes directly to a foreign destination (e.g. <a href="http://some.other.domain/foo/bar">) the session id visible in the logfile of the foreign site's webserver (via the Referer header). This is of course a security problem. Using the DerefServlet avoids this by using a redirect loop so the Referer header transmitted will no longer contain the session id.

AbstractPustefixXMLRequestHandler

This HTTP request handler, while still abstract, implements most of the output handling needed for request handlers that want use XSLT/XML to produce the final html sent to the browser.

The only thing a descendant needs to implement is the abstract method getDom(PfixServletRequest), whose return value can be thought of as a small container around a org.w3c.Document. The additional data stored in a SPDocument is among other things a map of XSLT transformation parameters that should be set and the "page name" which the system uses to choose the correct target stylesheet to transform the DOM with.

PustefixContextDirectOutputRequestHandler

In contrast to AbstractPustefixXMLRequestHandler and descendants, this request handler does not produce it's output by transforming XML with a XSLT stylesheet into HTML. There are situations where you need to stream some other format like pdf or images like PNG or GIF instead. The PustefixContextDirectOutputRequestHandler is used for exactly this purpose, because it delegates request processing to DirectOutputState objects which are allowed to write directly to the HttpServletResponse's OutputStream.

PustefixContextXMLRequestHandler

This is the main request handler that handles almost all pages in a typical Pustefix application - everything that produces HTML to be precise. The servlet doesn't do much on it's own, it delegates the request processing to a de.schlund.pfixcore.workflow.Context object. This Context is created by the PustefixContextXMLRequestHandler once for every session, stored into the HttpSession and reused for all later requests.

A Pustefix application has exactly one PustefixContextXMLRequestHandler. Although more than one additional >PustefixContextDirectOutputRequestHandler could be used, there is usually no need for such a configuration.

6.2. Processing of Requests

Every request that arrives at a pustefix application is processed in a specific way, depending on the HTTP request handler that is being selected by the PustefixHandlerMapping. In this chapter we will show how PustefixContextXMLRequestHandler handles requests.

The special processing of requests by this HTTP request handler is called the Pustefix Request Cycle. It is important to understand how this cycle works, and how to configure it in such a way to achieve the desired result for each request.

The main "director" in this cycle is the Context object, that will handle all the processing logic and call other objects to handle the business logic part of the request cycle (aka: do something useful with all the user supplied parameters of the request). The other main participants are implementations of the State and PageFlow interfaces.

Pustefix supplies default implementations of these interfaces.

6.2.1. The Context

The PustefixContextXMLRequestHandler does most of the request handling in an object called the "context". The interface Context provides the applications view on this object. From the applications view the context mainly is:

  • Providing an interface to de.schlund.pfixcore.workflow.ContextResource objects. These objects contain the data and the methods needed to implement the desired functionality of a project. Each Context objects initializes one de.schlund.pfixcore.workflow.ContextResourceManager, which in turn initializes all the requested ContextResources. All user data must be stored in ContextResources instead of directly into the HttpSession (this is by design, because a HttpSession only allows to store untyped String-to-Object relations, while the ContextResources can expose arbitrary complex access methods to the stored data).

  • Providing a pluggable authentication mechanism that is called before any request processing to check if the current session has the needed privileges.

  • Mapping of requested pagenames (aka "PageRequests") to the objects that implement the functionality that should be supplied by the page. The Context (with the help of a PageMap object initialized on startup of the Context) checks which page is requested and uses the associated de.schlund.pfixcore.workflow.State object to dispatch the request processing to. See below for more details on this process. Note that for each State only one instance is created, so no local data can be stored in States - all session data must be stored in ContextResources.

  • Organizing pages into PageFlows to provide a small scale "workflow management". PageFlows are linear lists of PageRequests which should be stepped through in order. The Context advances a PageFlow after a request has been handled sucessfully, ie. no error has happened as the result of processing the request data. The detailed rules on how page flows work and how the Context steps through them are explained below.

The context provides a de.schlund.pfixxml.SPDocument to the servlet. This class is a small wrapper around a org.w3c.Document and supplies the XML input document for the final transformation which produces the HTML output. Besides the DOM tree it contains the information the system needs to choose the right stylesheet for the desired page that is to be shown plus some other stuff like XSLT parameters that should be set for the transformation process. The Context doesn't produce the SPDocument itself but delegates this to the State's getDocument() method.

6.2.2. PageFlow

For the PageFlow interface there is currently only one implementation, and at the time of this writing it's not yet possible to change this implementation by supplying your own, although this is planned for the near future. The current implementation is called DataDrivenPageFlow, and it will be explained in more detail below. For the discussion presented here, it is sufficient to know the general PageFlow interface.

public interface PageFlow {
    String getName();
    String getRootName();
    boolean containsPage(String pagename);
    String findNextPage(PageFlowContext context, String currentpagename, boolean stopatcurrentpage,
                        boolean stopatnextaftercurrentpage) throws PustefixApplicationException;    
    boolean precedingFlowNeedsData(PageFlowContext context, String currentpagename) throws PustefixApplicationException;
    boolean hasHookAfterRequest(String currentpagename);
    void hookAfterRequest(Context context, ResultDocument resdoc) throws PustefixApplicationException, PustefixCoreException;
    void addPageFlowInfo(String currentpagename, Element root);
}
      

Both getName() and getRootName() return the name of the pageflow, the difference being that getName() contains the full qualified name (the root name together with any variant name, if present) while get RootName() only returns the root name.

containsPage(String pagename) must return true if the given page name is part of the page flow.

findNextPage(...) is more interesting. It implements the main duty of a page flow: To supply some sort of "next" page, given the context (in the form of a PageFlowContext which is just a stripped down version of the Context interface to only support getting information, but no changing the inner state of the context object), the information what the current page name is (currentpagename), and two flags:

  • stopatcurrentpage: If set to true and the current page is part of the pageflow, then the page flow searches no further for another matching page then the current page. I.e. for the linear page flows of the DataDrivenPageFlow implementation this means checking all the (accessible) pages starting at the head of the flow if they need data (that means: The associated State returns true for method call needsData(...)) - if yes, that page is returned. But if the page under consideration is the current page, return it in any case even if it doesn't need data.

  • stopatnextaftercurrent: This is is similar to the first flag, only that we don't stop the page flow search at the current page, but instead at the next accessible page after the current page.

The rest of the methods will be explained in more detail below.

6.2.3. States

The situation is different for States; Pustefix supplies implementations to cover most of the needs one may have in a normal application, however there are always situations where it is needed or at least much easier to write a specialized State instead of trying to re- or misuse one of the two "standard" implementations supplied with the framework.

These two implementations are StaticState and DefaultIWrapperState. The first is used for all static pages, i.e. pages that don't need to process any input parameters, but merely display more or less static content. The only dynamic thing this state can do is to include information from context resources into it's output DOM tree. The second one implements the concept of wrappers and handlers, which is the standard way in pustefix to handle input data.

Both of these states inherit from the abstract class StateImpl, a class that implements a bunch of helper methods useful for basically every conceivable state implementation. So it is strongly suggested to use this (or one of the two described states) as the base class for your own implementations.

While both of these states will be explained in detail below, it is important to note that the context only knows about states, not a special implementation of it. So on this level it makes no difference if a request supplies data to be processed, or if it only request the display of a certain page. So the only thing we need to know for this chapter is the interface all states have to implement:

public interface State {
    boolean        isAccessible(Context context, PfixServletRequest preq) throws Exception;
    boolean        needsData(Context context, PfixServletRequest preq) throws Exception;
    ResultDocument getDocument(Context context, PfixServletRequest preq) throws Exception;
}
      

These three methods are quite easy to explain. isAccessible(...) is used to check if a page is accessible (the exact wording would be "the associated State of the page", but we use page/state interchangeable here, as there is a n:1 association of pages to states anyway, i.e. every page has exactly one associated state, but most of the time many pages share the same state. States are singletons, so they don't store any data themselves. This allows to share them between many pages). getDocument(...) is the method that does all the work. Here we produce the result DOM tree that is used to render the final HTML page with. needsData(...) is (or better: can be) used only during page flow processing to determine what the next page is that needs to be shown. This method will be explained when we describe the PageFlow and it's default implementation in greater detail below.

6.2.4. Influencing the page request cycle

During the request-response cycle, the Context maintains a set of variables that influence the processing of the request. These are listed in the following table. Use these as a reference to see how they can be set and changed, either by specifying values for them in the request (directly, or by referencing an action that sets them) or by calling a method of Context somewhere from Java code during the processing of the request.

Table 6.1. Variables of the context during processing

Variable Type Usage How to set?
currentpagerequest PageRequest This is basically the object representing the current page we use to process the request. The value of currentpagerequest that is valid at the end of processing becomes the page to be displayed.

This variable must never be unset again after initialization during the whole request cycle (it only changes to other PageRequests).

Supplied by the request via either the first request path element (e.g. http://host.dom/PAGE?...) or if this is not given via the request parameter __page.

In Pustefix, this is usually transparent to the user: For POST requests, use the send-to-page attribute of pfx:forminput (this defaults to the current page if send-to-page is not given) and for GET requests, use the page attribute of pfx:button.

If no page information can be retrieved from the request, use the default page given in the configuration.

currentpageflow PageFlow Default is null. This object represents the currently valid page flow (if any). If it is null, there is no page flow selected. Can be explicitly set via the parameter __pageflow (in Pustefix used via the attribute pageflow to either pfx:button or pfx:xinp type="submit|image" via the request or by setting the pageflow attribute of a configured action).

Can also be set from Java by using the context method setCurrentPageFlow(String name).

The current page flow is very often not set explicitly, but selected automatically by the context. See below for a detailed explanation of the rules that apply in these cases.

prohibitcontinue boolean Default is false, if set to true during the request processing, the context will not use the pageflow (if any) to determine the next page to show, but instead use the currentpagerequest and display the associated page. Can be set from the outside by using the request parameter __forcestop=true (maps to setting the forcestop attribute of pfx:button or pfx:xinp type="submit|image" to true or by calling a configured action with this attribute set to true).

There is also a method in the context called prohibitContinue() to set this value to true. If by any means this value becomes true, there is no way to reset the value to false again!

jumptopage and jumptopageflow String Default is null for both. If set, jumptopage is interpreted as a page name that should be displayed after the current request is processed and only if prohibitcontinue is not set to true (in which case, as described below in more detail, no further processing takes place and the current page is displayed).

The jumptopageflow variable only has an effect if also jumptopage is set. It is used to set the current page flow to another page flow when jumpting to the target page of jumptopage.

With other words, this mechanism is used to jump to another page after the current request has been successfully handled.

This entry can be set quite similar to the pageflow variable above: We have __jumptopage and __jumptopageflow, normally created via the attributes jumptopage and jumptopageflow in either pfx:button, pfx:xinp type="submit|image" or a configured action.

There are also two methods in the context that can be used to set these values from Java: setJumpToPage(String name) and setJumpToPageFlow(String name).

stopnextforcurrentrequest boolean Default is false. Only has an effect if a page flow is set, and the current page is indeed a member of this page flow.

If set to true the pageflow is expected to return the next accessible page after the current page in the pageflow. The meaning of "after" depends on the implementation of the PageFlow used for the current flow. The default implementation (DataDrivenPageFlow) works with linear flows, so there is always a clear understanding of what is "before" and "after" a page in the flow. Other implementations may have a more complicated interpretation.

Can be set directly in a request by using __forcestop=step (and of course the same for the attributes to the Pustefix tags and configured actions).

This may seem strange, as that parameter is also used to set the prohibitcontinue variable, but as it makes no sense to specify both of them at the same time (prohibitcontinue effectively prohibits the use of a pageflow because the current page is being displayed anyway and no page flow is asked for the next page to display), there is no need to have an independent parameter or attribute.

startwithflow boolean Default is false. This variable is used to instruct the Context to not directly use the page that is submitted with the request (and which is still used to set the currentpagerequest variable), but instead ask the current pageflow for the next page to use. So the caller doesn't actually know which page will be the one to display. Most often, setting this parameter also implies explicitely setting a page flow via the methods listed above. We will cover this special case in more detail below. This variable can be set via the request parameter __startwithflow. With Pustefix tags this is achived by setting the startwithflow attribute of pfx:button to true.

There is no such possibility for pfx:xinp type="submit|image", because it makes no sense for a request which supplies data to not know where to submit to. Also using startwithflow="true" with pfx:button implies that the request will not being marked as one that sends data, even if there are pfx:argument nodes attached.


The currentpageflow variable needs some more explanation, as in many cases, it is not given explicitly neither by submitting an action that specifies the page flow nor directly from the request parameter __pageflow. If this is the case, the Context tries to find a matching page flow by using the following algorithm.

  1. If the current page is a member of exactly one page flow, this flow will become the current page flow.

  2. If the current page is a member of more than one page flow, the Context checks if one of these flows has been the last flow the system has used in any request before (so this even applies if the system didn't use a page flow at all during the last requests). If this is the case, the system uses this flow as the current page flow. This has the effect that a page flow will remain the current flow as long as the pages used for requests are at least a member of this flow.

  3. If the last flow isn't part of the list of flows matching the current page, the system checks if the current page specifies a defaultflow in its configuration (and makes sure that the page is really a member of this flow!). If yes, this flow is preferred and returned as the current page flow. If not, the first of the list will be returned.

  4. If the current page is not a member of any flow, the current page flow remains unset (the currentpageflow variable remains null).

6.2.5. The basic Pustefix Request Cycle

In this section we want to explain the way the request cycle is handled in Pustefix by the Context and its peer objects (State, PageFlow) used during processing.

After the variables have been initialized, we have two different ways to go on. Either startwithflow is set to true, or not. The first case will be explained below in more detail, for now we assume that the value of startwithflow is false. We also do neglect some other aspects, that have to be taken care of during request processing: Role based authentication isn't mentioned here and also the fact that each State will always be asked if it is accessible before calling one of the other two methods won't be mentioned explicitly for the remainder of this explanation.

  1. The first action to take is calling getDocument(...) on the State associated with the current page.

  2. If the current page flow has After-Request-Hooks defined (this is checked by calling the method hasHookAfterRequest(...) on the current PageFlow), these hooks are being run by calling hookAfterRequest(Context context, ResultDocument resdoc). The ResultDocument used here is the return value from the getDocument(...) call above. These hooks can basically do anything that can be achieved with the help of the Context interface (changing jumptopage/jumptopageflow, calling prohibitContinue() and so on). The interesting thing here is that they not only have access to the Context object, but also the resulting DOM-Tree of the processing of the current page. We will learn about an example of such hooks when we look at the current standard implementation DataDrivenPageFlow.

  3. Now we check if prohibitcontinue is set to true. If yes, the ResultDocument will be used to display the current page. The request cycle ends here.

  4. If prohibitcontinue is still false, check if jumptopage is set. If yes, set the currentpagerequest to the jumptopage (and also change currentpageflow to something that matches, preferring jumptopageflow, if it is set); jumptopage/jumptopageflow are unset to avoid recursion, then we re-enter the process at point 1.

  5. If jumptopage is not set, we try to use the current page flow to get the next page by calling findNextPage(..., ..., false, stopnextforcurrentrequest). We set this page to be the current page, call getDocument on its associated State and use the returned ResultDocument to display the page. The request cycle ends here (there is no recursive call of the page flow process!).

  6. If no current page flow is set (currentpageflow == null), we simply use the resulting ResultDocument of the initial call to getDocument(...) and use it to display the current page.

Accessibility of pages

Up to now, we mostly neglected the fact that a page could also be not accessible, which it is if the associated State returns false for a call to its method isAccessible(...). This method is checked each time before the Context tries to call getDocument(...) for a page. The PageFlow is also expected to only return a page that is accessible, and to check the accessibility before each call to needsData(...).

If the initially requested page is not accessible, but a page flow is set, the Context will try to find a matching page by calling findNextPage(..., ..., false, stopnextforcurrentrequest). Because this method is expected to only return a page that is accessible (if it doesn't find one, it must throw an exception) we can safely set this page to be the new current page and start with the request cycle as usual.

If no page flow is set, the system will try to use the default page from the configuration. If this is indeed accessible, it will be set as the new current page, and the whole process continues normally from there. If also the default page is not accessible, an exception will be thrown.

Usually this case won't happen, because you have to force Pustefix to generate a link to a page that is currently not accessible by using the mode="force" attribute on pfx:button. But this procedure is also done when the current page has been set from the jumptopage variable and the request cycle is being restarted (see above). To avoid to see any strange behavior and pages that have never been intended to be displayed, it's important to make sure that a jumptopage request only references a page that will be accessible.

This accessibility check must also be run by the PageFlow before calling needsData(...) or before returning any page name from findNextPage(...). It is expected to simply ignore any page that is not accessible and continue searching for another "next" page. As already explained above, if it is not able to find a page that is accessible, it must throw an exception.

Processing when startwithflow=true

Another change in request processing happens when the startwithflow variable is set to true. In this case, the context doesn't expect to get a valid pagerequest from the request itself (although there is one supplied, see below for the special meaning of this page name), so it has to "search" for the page to handle the request processing. This search is done by directly switching to the page flow handling part of the code to retrieve the "next" page that is suggested by the page flow.

Other than in the case of the usual page flow processing, there is some special trick involved: If during searching for the next page the page flow encounters the supplied page from the request, it will return this page regardless if it needs data or not. In other words: You can limit the search in the page flow by supplying a page that is part of this flow. If the page is not part of the page flow or it is inaccessible, it is ignored (But note that the default page flow implementation DataDrivenPageFlow also sets the supplied page to be the final page (see below for details on this), so essentially every supplied page becomes part of the page flow. But this behavior is not part of the contract between PageFlow and Context and just an implementation detail of DataDrivenPageFlow).

On a quick look, this seems to be almost the same as what is done when a request comes in to a page, that is not accessible (see above). In this case, too, a new page ist searched by asking the page flow for the next page. The main difference is that a "normal" request will always use the supplied page, as long as it is accessible, while in the startwithflow case it will only be used if no other "earlier" candidates have been returned by the page flow.

6.2.6. Pustefix State implementations

Despite all the explanation about States, most of the time one doesn't write States directly, but rather uses one of two predefined States (or trivial descendants of them).

StaticState

The first is de.schlund.pfixcore.workflow.app.StaticStateliteral>. This State is used when the page doesn't need to process any input data. This is true for most of the purely informative pages of a website (e.g. documentation, product information). StaticState returns true for any call to needsData, which makes it of course unusable for any non-trivial pageflow as described here. Also any call to isAccessible returns true. For the creation of the output DOM tree, the State respects the <output> nodes of the page definition in the context configuration file, but of course any <input> nodes are ignored.

DefaultIWrapperState and IHandlers

Of course it would be possible to write specialised States for every page that needs application logic. Experience shows however that the reuseable components of the application logic are not complete pages, but smaller parts that make up the whole functionality of the page. So we need a way to aggregate these smaller parts into a predefined State that delegates and distributes the calls made by the Context.

The State that implements this is called de.schlund.pfixcore.workflow.app.DefaultIWrapperState. The corresponding logical "atomic" components are implementations of de.schlund.pfixcore.generator.IHandler. Associated to these IHandlers are container classes (implementations of de.schlund.pfixcore.generator.IWrapper) that hold the user supplied input data that these handlers should work on. The IWrapper classes are autogenerated from a XML description that is explained in more detail here.

An IHandler has the following simple interface:

  • boolean needsData(Context)

  • boolean prerequisitesMet(Context)

  • boolean isActive(Context)

  • void retrieveCurrentStatus(Context, IWrapper)

  • void handleSubmittedData(Context, IWrapper)

The corresponding methods of the State interface and the mappings to the IHandler methods are:

  • boolean needsData(Context, PfixServletRequest): this method is just delegated to all defined IHandlers. If any of those returns true, the return value of the State methods is true, too and false otherwise.

  • boolean isAccessible(Context, PfixServletRequest): this method maps on the two IHandler methods prerequisitesMet and isActive. The default algorithm works like this: In a first round, on all IHandlers the prerequisitesMet() method is called. If any of the IHandlers returns false here, the return value of the whole State method is false. If all IHandler return true, a second iteration over the IHandler is made with a call to their isActive method. This time, the logic is reversed: if any IHandler returns true, the State method is true, too. It only returns false when no IHandler's isActive method returns true. It's possible to customize the behavior for this second iteration in the config file with the policy attribute of the <input> node.

  • ResultDocument getDocument(Context, PfixServletRequest): this method is mapped on either retrieveCurrentStatus or handleSubmittedData depending if the request is submitting user data by means of a GET request or a form submit (in this case handleSubmittedData would be called) or if the request just wants a page to be displayed (which would result in retrieveCurrentStatus to be called).

Some important things to note:

  • No IHandler method returns a ResultDocument (which is basically a wrapper around the DOM tree that is used for the final transformation to generate the HTML output). In a State it's possible (in fact needed) to create the ResultDocument itself and it's easy to put additional nodes into the DOM tree inside the getDocument method. By design, this no longer works with IHandlers. All output should be generated from the configured ContextRessources listed below the <output> node of the config file.

  • IHandlers don't have access to the PfixServletRequest (which is basically a wrapper around HttpServletRequest). All in- or output of parameters must take place via the typesave getter and setter methods of the associated IWrapper object. In the handleSubmittedData method one typically reads the values as they are supplied from the request, and in the retrieveCurrentStatus method one sets the parameters as they are given by the current status of the application to pre-fill form elements on the user interface (Note: the Pustefix elements to create form elements take care to automatically use the values as supplied via the IWrapper to pre-fill form elements. If you generate your form with other XSLT tags or with the original html elements, this will no longer work ot of the box).

The Context and DefaultIWrapperState

It's an important property of Pustefix that the Context doesn't know anything of IHandlers/IWrappers. From the view of the Context, it always works with States and nothing else. This section describes the way the DefaultIWrapperState and the Context play together.

A request can be processed in two different ways by the getDocument(...) method of DefaultIWrapperState:

  1. A request that submits data (as can be detected with the help of the method isSubmittrigger(...)) will be processed like this (EOR means end of request):

    • All IWrappers will initialize their data from the request.

    • On all IHandlers will be called handleSubmit(...) with the approbiate IWrapper object as a parameter.

    • The system checks if there has been an error during processing the reuqest.

      If yes, put all the error codes into the ResultDocument and tell the context to stop at the current page (by calling context.prohibitContinue()). EOR.

      If no, the system needs to decide to either stay on the current page (and display it again) or hand back the control to the Context to start a pageflow process that determines the next page to display. This decision is made be looking at the state of the context and the set of IHandlers. There are 3 different cases to consider:

      1. First we need to decide if there is anything that actively forces the system to stay on the page even in the case of a successful submit. Two cases must be consideres here: The context method prohibitContinue() has already been called (which can be checked by calling the Context method isProhibitContinueSet()). This will always force the context to stop the process, use the returned ResultDocument and display the current page. For the second case, depending on which IHandlers have been called to handle data (this can be a subset of all the IHandlers on the page, see here on how to restrict the submit to a subset of the full set of defined IHandlers), the DefaultIWrapperState decides to stay on the current page. A simplified explanation of the algorithm is like this: When only a subset of the IHandlers have been used to handle a data submiting request, force the system to stay on the current page. There is an exception to this rule, though: When all the IHandlers of the restricted subset are also marked with the continue attribute on the corresponding <interface> node in the config file set to true, then don't force the system to stay on the current page. All of this does NOT apply when the request has set a JumpToPage (this can be queried from the Context with the method isJumpToPageSet()) - in this case the JumpToPage takes precedence. NOTE: this will maybe be removed in the future and the default will be to try to continue with the pageflow in any case, despite any restricted subset of IHandlers.

      2. Alternatively we try to check if we really want to give control back to the Context to determine the next page. This will happen if one of three conditions is true:

        • A JumpToPage has been set.

        • The current page is part of the current pageflow

        • Or the current pageflow has been set explicitly by the request (in this case, the current page doesn't need to be a member of this flow).

      3. The default for the DefaultIWrapperState if none of the above conditions is true is to stay on the current page.

        If the result of these checks is to stay on the current page, call prohibitContinue() on the Context and call retrieveCurrentStatus() on the suitable IHandlers - suitable in this case means a) if no restriceted subset of IHandlers is selected, use all that are defined or b) use the restricted subset and additionally all IHandlers that have the alwaysretrieve attribute on their corresponding <interface> node in the config file set to true. EOR.

  2. A request for a page that doesn't submit data (direct trigger), or a request that comes in while a pageflow is processed or a request that is the result of a final page instruction.

    • All IWrappers will initialize their data from the request.

    • call retrieveCurrentStatus() on all the defined IHandlers, and force the Context to stop further processing by calling prohibitContinue(). EOR.

6.2.7. Pustefix PageFlow implementation: DataDrivenPageFlow

6.3. The data model: Context resources

Pustefix provides an abstraction layer for management of application state. You generally don't directly work on the Servlet API layer, e.g. you don't directly store session state in the HttpSession object, but you use Java beans which are automatically managed by the framework, so-called ContextResource beans.

In prior versions Pustefix itself controlled the lifecycle of these beans, e.g. instantiated them on session startup, and you had to implement a specific interface (de.schlund.pfixcore.workflow.ContextResource). Beginning with Pustefix 0.13 ContextResource beans are automatically managed by Spring and can be arbitrary POJOs.

ContextResource beans are not only used to manage application state, but they are also linked to the view. They can be declaratively assigned to pages and thus serialized to the XML tree, which is used as source of the XSL transformation producing the output. A ContextResource can either be automatically serialized to XML, or it can implement its own method manually creating according DOM output.

6.4. Wrappers and Handlers

Write introduction...

6.4.1. IWrappers

IWrappers are used to store the request data that is used as input for corresponding IHandler classes. IWrappers are not explicitly written by the developer, but are generated from .iwrp files which contain a high level XML description of the parameter types and certain checks that should be applied to them. Each .iwrp file is translated into a .java file with the same name during the build process, and a java class is created. In the corresponding IHandler, you use the IWrapper to access or store data by calling the IWrapper's get and set methods.

The syntax of the .iwrp file is given below.

<interface xmlns="http://www.pustefix-framework.org/2008/namespace/iwrapper"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/iwrapper 
                      http://www.pustefix-framework.org/2008/namespace/iwrapper.xsd"
  extends="mypackage.MyIWrapperClass">
  <!--
    The class attribute references the associated IHandler class. Alternatively you can
    reference an existing Spring bean using the bean-ref attribute.
    The ihandler node is optional, but if it is left out, the generated
    IWrapper will be abstract and cannot be used directly, unless an extends attribute
    has been given to the interface node. Normally you will want to specify an ihandler
    class here.
  -->
  <ihandler class="mypackage.MyIHandlerClass"/>
  <!-- or <ihandler bean-ref="aBean"/> -->
  <!--
    This node can (and usually will) occur multiple times, one for every parameter that
    should be part of the interface
  -->
  <param name="AName" type="some.java.Type" occurrence="optional|mandatory|indexed" frequency="single|multiple"
         missingscode="some.defined.statuscode">
    <!--
      The whole node is optional. It allows to specify a default value (or multiple) for
      a parameter to use, when no value is supplied via the request. Note that this makes
      the destinction between optional and mandatory parameters nonsensical.
    -->
    <default>
      <value>a_default_value</value>
      <value>an_other_default_value</value>
    </default>
      
    <!--
      To test if a param value conforms to the rules, you can specify Check-Classes here
      whose check method will be called to test if the param value makes sense.
      The Check-Classes must implement de.schlund.pfixcore.generator.IWrapperParamPreCheck
      (the interface defines the check(...) method).
    -->
    <precheck class="a.prechecker.class">
      <cparam name="APreCheckerParamName" value="APreCheckerParamValue"></cparam>
    </precheck>
      
    <!--
      Each parameter must be casted from a String to the specific type (unless the type is java.lang.String itself,
      in this case, no caster need to be supplied). This is done by means of a class implementing 
      de.schlund.pfixcore.generator.IWrapperParamCaster. For the usual simple types you can use a caster from the
      package de.schlund.pfixcore.generator.casters.
    -->
    <caster class="de.caster.class">
      <cparam name="ACasterParamName" value="ACasterParamValue"></cparam>
    </caster>

    <postcheck class="a.postchecker.class">
      <cparam name="APostCheckerParamName" value="APostCheckerParamValue"></cparam>
    </postcheck>
  </param>
</interface>

Parameters

When defining the parameters for your wrapper, the following attributes are supported in the iwrp format:

Table 6.2. Attributes of an iwrp parameter

Attribute name Mandatory? Description
name mandatory The name of the parameter. This is used in the getter and setter methods that are generated. E.g. for the parameter name Foo there will be - amongst others - a corresponding java method called getFoo.
type mandatory The java type of the parameter. This will determine the return type of the generated getter method.
occurrence Optional, default is mandatory This attribute specifies if the parameter must be given (mandatory), or if it's not considered to be an error if it is omitted (optional). The special value indexed tells the system that it should search for occurrences of the parameter name with a suffix appended of the form AParamName.ASuffix. The suffix string must be unique for every occurrence of the parameter named AParamName. Indexed parameters are never mandatory.
frequency Optional, default is single This attribute specifies if only one parameter of the same name should be accepted or multiple. This determines if the generated getter method's return value is a single object or an array.
missingscode Optional, default is de.schlund.pfixcore.generator.MISSING_PARAM This attribute applies only to mandatory parameters. It allows to specify a different than the default StatusCode to use when the parameter is not supplied.


IWrappers support different parameter types that result in different method signatures in the generated wrapper code.

The parameter type is influenced by the attributes occurrence and frequency

Single mandatory/optional parameters

In most cases, your wrappers will be used to accept and validate simple input parameters, like data entered in input fields. These parameters are created setting frequency to single and occurrence to mandatory or optional.

This will lead to the following methods:

public class MyWrapper {
    Bar  getFoo();
    void setFoo(Bar value);
    void setStringValFoo(String str_value);
    void addSCodeFoo(de.schlund.util.statuscode.StatusCode scode);
    void addSCodeWithArgsFoo(de.schlund.util.statuscode.StatusCode scode, String[] args);
}
Multiple mandatory/optional parameters

In some cases, a parameter might be present in a page more than once. This might be the case for a list of checkboxes, where all input tags have the same name, but different values. These parameters are created setting frequency to multiple and occurrence to mandatory or optional.

This will lead to the following methods:

public class MyWrapper {
    Bar[] getFoo();
    void  setFoo(Bar[] value);
    void  setStringValFoo(String[] str_value);
    void  addSCodeFoo(de.schlund.util.statuscode.StatusCode scode);
    void  addSCodeWithArgsFoo(de.schlund.util.statuscode.StatusCode scode, String[] args);
}
Single indexed parameters

Pustefix also allows you to repeat a parameter inside a page/request and access all occurrences of the parameter using an index. In the request, the parameter and the index are separated using a . (dot).

These parameters are created setting frequency to single and occurrence to indexed.

This will lead to the following methods:

public class MyWrapper {
    Bar  getFoo(String index);
    void setFoo(Bar value, String index);
    void setStringValFoo(String str_value, String index);
    void addSCodeFoo(de.schlund.util.statuscode.StatusCode scode, String index);
    void addSCodeWithArgsFoo(de.schlund.util.statuscode.StatusCode scode, String[] args, String index);
}

This is very helpful, if you have a list of entities (like users, books, etc.) and it is possible to edit the data of several of these entities in one form.

Multiple indexed parameters

Of course, it is also possible to combine multiple and indexed parameters in a wrapper.

These parameters are created setting frequency to multiple and occurrence to indexed.

This will lead to the following methods:

public class MyWrapper {
    Bar[] getFoo(String index);
    void  setFoo(Bar[] value, String index);
    void  setStringValFoo(String[] str_value, String index);
    void  addSCodeFoo(de.schlund.util.statuscode.StatusCode scode, String index);
    void  addSCodeWithArgsFoo(de.schlund.util.statuscode.StatusCode scode, String[] args, String index);
}

6.4.2. The InputHandler interface

The InputHandler interface is the replacement for the deprecated IHandler interface. Its advantages are: you get rid of casting because it's generic, and the methods don't declare to throw Exception, which forces sensible exception handling.

public interface InputHandler<T extends IWrapper> {
    void    handleSubmittedData(T wrapper);
    void    retrieveCurrentStatus(T wrapper);
    boolean prerequisitesMet();
    boolean isActive();
    boolean needsData();
}

6.4.3. The deprecated IHandler interface

The IHandler interface is deprecated. Use InputHandler instead.

public interface IHandler {
    void    handleSubmittedData(Context context, IWrapper wrapper) throws Exception;
    void    retrieveCurrentStatus(Context context, IWrapper wrapper) throws Exception;
    boolean prerequisitesMet(Context context) throws Exception;
    boolean isActive(Context context) throws Exception;
    boolean needsData(Context context) throws Exception;
}

6.5. StatusCodes

StatusCodes are assigned to IWrapper parameters and form fields to indicate invalid data and display according error messages. They are automatically assigned by the framework when prechecks, casters or postchecks fail, or they can be assigned programmatically by the application logic.

The framework predefines some common StatusCodes, e.g. MISSING_PARAM - indicating the missing of a mandatory form value, or StatusCodes for builtin prechecks, casters and postchecks, like precheck.REGEXP_NO_MATCH - indicating that a form field's value doesn't match a given regular expression. Application developers can arbitrarily define additional StatusCodes.

StatusCodes are defined as normal include parts. They're placed in their own files located under the dyntxt directory. Within such a statuscode file the part names uniquely identify the StatusCode, the content nested inside the part element represents the message.

<?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="ILLEGAL_LOGIN">
    <theme name="default">Illegal login data.</theme>
  </part>
  ...
</include_parts>

Those StatusCode files are used by the build process to automatically create Java classes containing the StatusCodes as constants, using the part names as field names (in uppercase form). Thus the StatusCodes can be safely referenced from within Java code.

Before the StatusCode files are recognized by the build process, they have to be configured inside a statuscode metainformation file. The file has to be called statuscodeinfo.xml and has to be located in the project's conf directory or in the dyntxt directory. Within this file you define which StatusCode constant class is built from which StatusCode files.

<?xml version="1.0" encoding="UTF-8"?>
<statuscodeinfo xmlns="http://www.pustefix-framework.org/2008/namespace/statuscodeinfo" 
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/statuscodeinfo 
                                    http://www.pustefix-framework.org/2008/namespace/statuscodeinfo.xsd">
  <statuscodes class="example.bank.BankStatusCodes">
    <file>statusmessages.xml</file>
    <file>...</file>
  </statuscodes>
  ...
</statuscodeinfo>

Using the above examples Pustefix will build the following Java class (excerpt):

package example.bank;
...
public class BankStatusCodes {
    public static StatusCode getStatusCodeByName(String name) { ... }
    ...
    public static final StatusCode ILLEGAL_LOGIN = new StatusCode("ILLEGAL_LOGIN", __RES[0]);
}

The following login handling example uses the generated StatusCode class to indicate a failed login:

public class LoginHandler implements InputHandler<Login> {
    public void handleSubmittedData(Login login) {
        //check login data
        //if invalid
        login.addSCodeUser(BankStatusCodes.ILLEGAL_LOGIN);
    }
}

If you take a look at the resulting XML tree, you see like the according include part is referenced inside the error element (and thus can be displayed on the page using the pfx:checkfield, pfx:error and pfx:scode tags).

<formresult ...>
   ...
   <formvalues>
      <param name="login.User">xyz</param>
      ...
   </formvalues>
   <formerrors>
     <error name="login.User">
        <pfx:include href="samplebank/dyntxt/statusmessages.xml" part="ILLEGAL_LOGIN"/>
     </error>
   </formerrors>
   ...
</formresult>

If your error message should contain dynamic data, you can use placeholders, which will be replaced by the arguments passed along with the addScode call at the IWrapper:

<part name="ILLEGAL_LOGIN">
  <theme name="default">User <pfx:argref pos="1"/> is illegal.</theme>
</part>

StatusCodes can be also referenced from within IWrapper definitions. If you're using a built-in StatusCode (from org.pustefixframework.generated.CoreStatusCodes), you can just use its name, if you're using a StatusCode from another module, you have to prefix the name with the class name of the generated class separated by a # sign:

<interface xmlns="http://www.pustefix-framework.org/2008/namespace/iwrapper">

  <ihandler class="..."/>
  
  <param name="Login" type="..." missingscode="example.bank.BankStatusCodes#MISSING_LOGIN_DATA">
    ...
  </param>

</interface> 

The statusmessage files for statuscodes coming from the Pustefix core or arbitrary modules can be edited to customize the messages for your project's requirements. Therefor the original statusmessage files are initially copied or if a copy already exists, the files are merged (statusmessages for new statuscodes are added, statusmessages for omitted statuscodes are removed) by the build process. You should only customize these merged files, changes in the original files will be overwritten with the next module update.

The built-in core and editor statuscodes are merged to projects/core-override/dyntxt/statusmessages-core-merged.xml and projects/core-override/dyntxt/statusmessages-editor-merged.xml. Pustefix module statuscodes are merged to projects/modules-override/MODULENAME/dyntxt/statusmessages-merged.xml.

6.6. ContextInterceptors

Pustefix provides a mechanism to hook custom logic into the request processing lifecycle. Therefor it provides a set of interception points, where you can register classes or Spring beans implementing the ContextInterceptor interface, which then are automatically called during the processing of a request.

Common use cases for ContextInterceptors are:

  • Implementation of cross-cutting concerns like logging, cleanup.

  • Implementation of standard design patterns like Hibernate's "Open Session in view".

  • Triggering logic/setting data independent of the requested page, e.g. setting a variant.

  • Handling of common, page-independent/shared parameters.

Figure 6.2. ContextInterceptors

ContextInterceptors


Currently Pustefix supports three interception points: start, which is right before the actual request handling gets started, end, which is right after the DOM result tree has been created, and postrender, which is right after the view has been rendered.

public interface ContextInterceptor {
    void process(Context context, PfixServletRequest preq);
}

The framework calls the process method of the registered ContextInterceptor implementations, passing the Context and PfixServletRequest as parameters.

[Note]Note
Comparison with ServletFilters: ServletFilters apply right before/right after a request/response enters/leaves the framework's processing layer and give you means to manipulate the incoming ServletRequest and outgoing ServletResponse, whereas ContextInterceptors are part of the framework's lifecycle and give you direct access to the request (but not the response), Context and other Spring beans, and are meant to trigger logic or set data.

ContextInterceptors are configured in the Context's configuration file. Below you can see an a sample configuration fragment showing all available configuration options:

<context-xml-service-config>
 
   <context> ... </context>

   <!-- ContextInterceptors are defined within the <interceptors> element directly following the <context> element. -->
   <interceptors>

     <!-- The interceptors are grouped by their interception points (start, end, postrender), which are represented 
            by accordingly named elements. -->
     <start>

        <!-- Interceptors are registered either by setting the ContextInterceptor implementation class or by setting 
               an according reference to a Spring bean configured elsewhere. -->
        <interceptor class="mypackage.MyStartInterceptor"/>

        <!-- If you're setting an implementation class, the framework will create an according Spring bean for you. 
               By default it will get Singleton scope, but you can provide an alternative scope using the scope attribute. --> 
        <interceptor class="mypackage.MySessionScopedInterceptor" scope="session"/>

        <interceptor bean-ref="myStartInterceptor"/>

     </start>

     <end> ... </end>

     <postrender> ... </postrender>

   </interceptors>

   ...

</context-xml-service-config>

The following example interceptor tries to guess from which country the client request is coming and sets a special variant if it comes from Germany. Therefor it checks the client's IP retrieved from the servlet request using a GeoLocationAPI. It sets a flag after the first check to avoid multiple checks. Remembering the flag this way only works when the interceptor has session scope and synchronization can be omitted because the operation is repeatable and boolean access is atomic.

public class MySessionScopedInterceptor implements ContextInterceptor {
	
	private boolean checked;

	@Override
	public void process(Context context, PfixServletRequest preq) {
		if(!checked) {
			String country = GeoLocationAPI.getCountry(preq.getRemoteAddr());
			if(country.equals("DE")) context.setVariant("foo");
			checked = true;
		}
	}
	
}
[Note]Note

ContextInterceptors are singleton-scoped beans by default. So if you want to store data or call non-threadsafe code, you possibly will have to do some synchronization or use an appropriate scope.

ContextInterceptors are called on every request. So you shouldn't execute long-running tasks and avoid that time-consuming code is unnecessarily called with each invocation.

Chapter 7. Advanced topics

7.1. Variants and Themes

7.1.1. Themes

Describe themes here.

7.1.2. Variants

Variants are a way to have the same page look and behave different depending on a variant id that can be set freely at runtime. This allows to select different look and behaviour depending on data collected during the user sesion.

Selecting a variant

Variants can be selected by calling the method setVariant(Variant var) in the Context. This will store and reuse the id for the current and all following requests until it is set to a different id (or erased by setting it to null).

The Context takes care to set the variant in the SPDocument it returnes to the AbstractXMLServer which in turn tries to get the matching target for the requested page and requested variant. If no target matching the variant is found, the "root" variant (in other words, the page without a variant at all) will be tried.

When defining variants of pages, the system automatically makes sure that you can only define variants for a page that has also a no-variant definition. This works for pages defined using the <standardpage> tag and for pages defined by hand using <target> tags directly.

The valid characters for variants are a-zA-Z0-9_+- (but see below for compound variants and the use of the : character).

Compound Variants

Variants can be variants of variants themself which in turn can be considered variants of another variant. The name of a variant that is a subvariant of another variant must be expressed in the following way: foo:bar:baz

Here we have a variant that is a subvariant of foo:bar which in turn is a subvariant of foo. This information is used when determining the right Target for generating the output. Say the variant foo:bar:baz has been set via the setVariant(Variant var) method of the Context and the request wants to display the page Home. When trying to get the matching Target for transforming the output the system first tries to get the matching Target for Home, variant foo:bar:baz. If it gets no result, it goes on trying Home, variant foo:bar; then Home, variant foo before finally requesting the "root" variant of page Home.

Variant features

Variants can influence the way an application works:

  • They can change the look and content of a page. This is implemented in terms of themes that depend on the selected variant. See below for more details.
  • They can change the backend definition of a pagerequest (aka: Handlers, State, and output from ContextRessources)
  • They can change the definition of a pageflow

Defining variants of pages

Different variants of pages are usually defined via the standardpage tag:

<standardpage xml="MyProject/xml/frame.xml" name="Home" variant="foo:bar:baz">

What's interesting here is how the system produces the themes attribute used for the two targets that result from the expansion of the standardpage tag.

If we have not given a special local themes attribute to the standardpage tag, and no global themes attribute has been given (see here), then the value of the themes attribute for both targets is in this example baz bar foo MyProject default.

As can be seen, the use of the variant attribute will expand the themes as they would be valid for the targets produced from the standardpage tag (either by using a locally defined themes attribute of the standardpage tag, or automatically by using the global fallback) by putting more specific themes in front of the themes list, which are generated from the given variant id. If the variant id is a compount variant, then each segment of the compound variant will be used, from right to left, to generate a theme name to be put before the "normal" themes valid for the page.

If the standardpage had its own themes attribute given (say for example "theme_a theme_b default"), the resulting themes after taking the variant id into account would of course be baz bar foo theme_a theme_b default.

Defining variants of pagerequests

Variants can also be used to create different implementations for the same pagerequest. This is done by a simple extension to the way a pagerequest is defined in the servlet configuration file.

<pagerequest name="foo">
  <default>
    [Any other tag that is allowed inside a pagerequest]
  </default>
  <variant name="var_id">
    [Any other tag that is allowed inside a pagerequest]
  </variant>
  <variant name="another_var_id">
    [Any other tag that is allowed inside a pagerequest]
  </variant>
 </pagerequest>
[Note]Note

You must give a <default> definition when using named variants.

Each of the <default> and all <variant> definitions are self contained, full definitions of pagerequests. Nothing is shared between them.

Defining variants of pageflows

Variants can also be used to create different implementations for the same pageflow. This is done by a simple extension to the way a pageflow is defined in the servlet configuration file.

<pageflow name="fooFlow">
  <default>
    [Any other tag that is allowed inside a pageflow]
  </default>
  <variant name="var_id">
    [Any other tag that is allowed inside a pageflow]
  </variant>
  <variant name="another_var_id">
    [Any other tag that is allowed inside a pageflow]
  </variant>
</pageflow>
[Note]Note

You must give a <default> definition when using named variants.

Each of the <default> and all <variant> definitions are self contained, full definitions of pagerequests. Nothing is shared between them.

7.2. Page alternatives

Page alternatives are different representations of the same logical page. Page alternatives are internally used to create different versions for languages/tenants, but can be also used for pages which are based on a common page, assigned to the same logical page, but with different content and display pagename, e.g. landing pages (see Section 4.3.4, “Sitemap configuration”).

Conceptually related to page variants, page alternatives are implemented on a lower level. Creating a page alternative automatically creates alternatives for all variants of a page, i.e. all existing standardpage definitions of a page are copied, thus providing an own stylesheet per alternative.

The definition of page alternatives is done within the sitemap configuration:

<sitemap>
  <page name="Info">
    <alt key="cities" name="Cities"/>
    <alt key="nationalparks" name="NationalParks"/>
    <alt key="mountains" name="Mountains"/>
  </page>
</sitemap>

Every page alternative has a key, which is referred to select the matching alternative. The key can be programmatically set using the Context method setPageAlternative(String key). During the XSL transformation the key can be accessed using the parameter $pageAlternative. The key will be also inserted into the theme fallback chain. Thus you can produce different content per alternative.

7.3. Internationalization

Pustefix provides various mechanisms to implement internationalized applications. You can write applications which have a fixed language which is selected/resolved at buildtime using the theme mechanism (see Section 7.1, “Variants and Themes”) or the dynamic include mechanism (see Section 7.5, “Dynamic resource inclusion”). You can make the language switchable at runtime using the language-aware core tags (see Section 5.6.2, “Displaying content based on the language”).

Using Pustefix's multitenancy support (see Section 7.4, “Multitenancy” you get a combination of both mechanisms. Pages have a fixed language, but an own instance is created for each language, and thus the language is switchable at runtime too.

Your business logic can query the currently selected language using the Context method getLanguage(), using setLanguage(String language), you can change the selected language at runtime.

7.4. Multitenancy

Pustefix provides basic multitenancy support, i.e. a single application instance can serve multiple tenants, like markets or countries. Tenants are automatically selected by matching host name patterns against the requested host name. Tenant configuration is done within project.xml or included configuration fragments (see Section 4.3.2, “Project descriptor (project.xml)”).

<project-config>
  
  <tenant name="CA_market">
    <choose>
      <when test="$mode='prod'">
        <host>.*\.ca$</host>
      </when>
      <otherwise>
        <host>^ca\..*</host>
      </otherwise>
    </choose>
    <lang default="true">en_CA</lang>
    <lang>fr_CA</lang>
  </tenant>

  <!-- include tenants config fragments from all modules found in classpath -->
  <config-include file="conf/project-fragment.xml" section="tenants" module="*"/>
  
</project-config>

The current multitenancy support concentrates on the view layer. Pustefix automatically creates tenant and language specific versions of all pages. Tenant/language specific page content is created by applying filters to the dynamic include mechanism, thus overriding and extending the core page content with tenant- and/or language-specific content from according tenant/language modules (see the following examples of a tenant and language Pustefix module descriptor, also see (Section 9.1, “Resources within library JARs”).

<module-descriptor>

  <module-name>pustefix-i18n-tenant-ca</module-name>

  <default-search priority="1">
    <filter-attribute name="tenant" value="CA_market"/>
  </default-search>
  
</module-descriptor>
<module-descriptor>

  <module-name>pustefix-i18n-lang-fr</module-name>

  <override-modules>
    <filter-attribute name="lang" value="fr"/>
    ...
  </override-modules>

</module-descriptor>

Application developers can query the currently set tenant at runtime using the Context method getTenant(). Additionally Pustefix automatically adds an element <tenant> to the result DOM tree. During XSL transformations the tenant targeted by the transformation can be accessed using the XSL parameter $tenant.

Static resources, like error pages, can also be delivered in a language/tenant-specific way. Optionally resources can be searched using tenant and language as path components, e.g. with tenant set to "Canada" and language set to "fr_CA", the static page /pages/error404.html will be searched in the following locations (highest priority first):

/pages/Canada/fr_CA/error404.html
/pages/Canada/fr/error404.html
/pages/Canada/error404.html
/pages/fr_CA/error404.html
/pages/fr/error404.html
/pages/error404.html

7.5. Dynamic resource inclusion

Pustefix applications can be modularized by packaging/sharing application parts or components as Pustefix modules, which are regular Jar files with a defined directory structure and a special deployment descriptor (see Chapter 9, Module Support). Pustefix modules not only contain Java classes, but can contain any kind of resource, like XML, XSL, Javascript, CSS or image files.

Prior Pustefix releases required that these resources were extracted to the file system by the build process to be usable in an application. Meanwhile Pustefix supports that XML and XSL files used by the rendering engine (TargetGenerator) can be directly loaded from the module jar files.

Developing strongly modularized applications often requires, that you want to be able to adapt parts of a shared module to your application's context without breaking the modularization. Therefor Pustefix supports the dynamic inclusion of resources, which allows you to provide a local version of a resource, which will be automatically taken instead of the according version from a module jar. Additionally resources from modules can be overridden by other modules.

7.5.1. Including resources from modules

Resources from modules are directly used by additionally specifying the name of the module. Therefor the according tags from the Pustefix tag library and the configuration elements in depend.xml provide a module attribute.

samplemodule.jar

META-INF/pustefix-module.xml
PUSTEFIX-INF/img/image.png
PUSTEFIX-INF/img/theme/image.png
PUSTEFIX-INF/txt/common.xml
PUSTEFIX-INF/txt/pages/main_page.xml
PUSTEFIX-INF/xml/frame.xml
PUSTEFIX-INF/xsl/metatags.xsl
PUSTEFIX-INF/xsl/skin.xsl

Let's take a look at how the various resources from the sample module can be directly included using the Pustefix tag library:

<pfx:xinp type="image" src="img/image.png" module="samplemodule"/>

<pfx:image themed-path="img" themed-img="image.png" module="samplemodule"/>

<pfx:include part="foo" href="txt/common.xml" module="samplemodule"/>

<pfx:maincontent part="content" path="txt/pages" module="samplemodule"/>

You just have to set the module attribute and be aware that the file paths are relative to the PUSTEFIX-INF root folder from the module jar file.

[Note]Note
If you're using includes from within a module and you're omitting the href or module attribute, you should be aware of the following behaviour:
  • if you're only specifying the part attribute, the include is loaded from the current file from within the current module
  • if you're specifying part and href, the include is loaded from the specified href/path from within the current module
  • if you're specifying part and module, the include is loaded from the href/path of the current file from within the specified module
[Note]Special module names
If you want to explicitly reference an include from the application, you can use the special module name webapp, which indicates Pustefix, that it shouldn't look in the current module as done by default, but load the file from the webapp folder. If you want to explicitly reference an include from the module where the page is defined (where the config fragment with the standardpage definition resides), you can use the special name pagedef.

If you want to directly reference XML/XSL files in the depend.xml configuration file, this works the same way by adding a module attribute to the according elements:

<standardmaster>
  <include stylesheet="xsl/skin.xsl" module="samplemodule"/>
</standardmaster>
<standardmetatags>
  <include stylesheet="xsl/metatags.xsl" module="samplemodule"/>
</standardmetatags>
<standardpage name="page" xml="xml/frame.xml" module="samplemodule"/>

Including or importing a stylesheet from a module into another stylesheet can be done using a module URI:

<xsl:import href="module://samplemodule/xsl/test.xsl"/>

A module URI complies with the hierarchical URI syntax [scheme:][//authority][path][?query][#fragment]. The scheme has to be set to module and the authority part is set to the module name. The path has to be relative to the PUSTEFIX-INF folder.

Such URIs also can be programmatically used to get a module resource with the ResourceUtil helper class:

Resource res = ResourceUtil.getResource("module://samplemodule/txt/test.txt");

7.5.2. Dynamic search

Resources included from modules can be overridden by providing an alternative version in the project itself or in the common folder. Resources included from the common folder can be overridden by a project specific version as well.

Therefor you have to place the overriding file under the same relative path in your project/application or the common directory and set the search attribute of the according Pustefix tag to dynamic.

<pfx:include part="foo" href="txt/common.xml" module="samplemodule" search="dynamic"/>
<pfx:image src="img/image.png" module="samplemodule" search="dynamic"/>

Setting search to dynamic the resource will be searched at different pre-defined locations in the following order:

  • In the project's folder: projects/myproject/path/to/resource
  • In the common folder: projects/common/path/to/resource
  • In all overriding modules (see next section): PUSTEFIX-INF/path/to/resource
  • In the specified module: PUSTEFIX-INF/path/to/resource

If you omit the module attribute, the resource will be only searched in the project's and the common folder.

[Note]Note
If you're dynamically searching for an include part and a file is found in the fallback chain, but doesn't contain the requested part, the search will move along to the next level. In contrast, themes aren't taken into account, i.e. the first file containing the searched part will be returned, regardless of how specific the contained themes are.
<pfx:image themed-path="img" themed-img="image.png" module="samplemodule" search="dynamic"/>

If you're dynamically including a themed image, the search iterates over the theme list on each fallback level, e.g. the theme fallback list "foo bar default" will result in the following search:

projects/myproject/img/foo/image.png
projects/myproject/img/bar/image.png
projects/myproject/img/default/image.png
projects/common/img/foo/image.png
projects/common/img/bar/image.png
projects/common/img/default/image.png
...
      
[Note]Note
If you're dynamically searching for a themed image and a matching file is found, the returned image will be the one with the most specific theme on the current fallback level, images matching more specific themes down in the fallback chain aren't taken into account.

7.5.3. Overriding module resources

Resources from modules can be overridden by other modules. Therefor a module can declare, which resources from which module it wants to override. This declaration is done within its module descriptor (see Chapter 9, Module Support).

<module-descriptor 
     xmlns="http://www.pustefix-framework.org/2008/namespace/module-descriptor"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/module-descriptor
                         http://www.pustefix-framework.org/2008/namespace/module-descriptor.xsd">

  <module-name>module-B</module-name>

  <override-modules>
    <module name="module-A">
      <resource path="txt/common.xml"/>
    </module>
  </override-modules>

</module-descriptor>

In this example module-B overrides a resource of module-A. This means that module-B is inserted into the fallback chain for the resource right after the common folder and right before module-A.

<pfx:include part="foo" href="txt/common.xml" module="module-A" search="dynamic"/>

Trying to dynamically include part foo from txt/common.xml and module-A will result in the following fallback chain:

projects/myproject/txt/common.xml
projects/common/txt/common.xml
module://module-B/txt/common.xml
module://module-A/txt/common.xml

An overriding module itself can be overridden by another module. Consider the following descriptor for sample-module-C:

<module-descriptor 
     xmlns="http://www.pustefix-framework.org/2008/namespace/module-descriptor"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/module-descriptor
                         http://www.pustefix-framework.org/2008/namespace/module-descriptor.xsd">

  <module-name>module-C</module-name>

  <override-modules>
    <module name="module-B">
      <resource path="txt/common.xml"/>
    </module>
  </override-modules>

</module-descriptor>

This will result in the following new fallback chain:

projects/myproject/txt/common.xml
projects/common/txt/common.xml
module://module-C/txt/common.xml
module://module-B/txt/common.xml
module://module-A/txt/common.xml

Now we add another module, called module-A-ext, which is also overriding module-A:

<module-descriptor 
     xmlns="http://www.pustefix-framework.org/2008/namespace/module-descriptor"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/module-descriptor
                         http://www.pustefix-framework.org/2008/namespace/module-descriptor.xsd">

  <module-name>module-A-ext</module-name>

  <override-modules>
    <module name="module-A">
      <resource path="txt/common.xml"/>
    </module>
  </override-modules>

</module-descriptor>

The new fallback chain looks as follows:

projects/myproject/txt/common.xml
projects/common/txt/common.xml
module://module-C/txt/common.xml
module://module-B/txt/common.xml
module://module-A-ext/txt/common.xml
module://module-A/txt/common.xml

Module module-A-ext is inserted right before module-A. This is done because the override mechanism processes the modules in alphabetic order to ensure predictable behaviour, i.e. it iterates over all modules overriding the current module resource, adding the overriding module on top of the module fallback list, and then recursively processes this module the same way. So changing the module name to module-D would have been resulted in placing the module right before module-C.

7.6. Authentication and authorization

Pustefix provides a role-based authorization mechanism. You can define arbitrary roles, declare logical operations/combinations on this roles using authconstraints, and assign these authconstraints to pagerequests.

7.6.1. Managing Roles

A role is defined using an according XML element with a unique name attribute value. Setting the initial attribute to true the role will be automatically set on context initialization.

<role name="MYROLE" initial="true"/>

Authconstraints can combine various authorization conditions, supported conditions are: hasrole, and, or and not, represented by according XML elements. Using the authpage attribute you can define the page, which should be called on authorization failure. Using the default attribute you can set one toplevel authconstraint to be the default one for all pagerequests having no authconstraint asssigned.

[Tip]Tip

In addition to the authpage-attribute one can define more fine-grained, conditional navigation-cases via the new navigateTo-child-element to authconstraint.

<authconstraint id="MYCONSTRAINT" authpage="login" default="true">
  <or>
    <hasrole name="MYROLE"/>
    <hasrole name="OTHERROLE"/>
  </or>
  <!-- navigate to page guests, if constraints above fail, but user still has the "GUEST"-role -->
  <navigateTo page="guests">
    <hasrole name="GUEST"/>
  </navigateTo>
</authconstraint>

Pagerequests can either define new authconstraints as child elements or can reference existing toplevel authconstraints by their id.

<pagerequest name="mypage">
  <authconstraint ref="MYCONSTRAINT"/>
  ...
</pagerequest>

<pagerequest name="mypage">
  <authconstraint authpage="login">
    <hasrole name="MYROLE"/>
  </authconstraint>
  ...
</pagerequest>

You can programmatically set/query roles using the de.schlund.pfixcore.auth.Authentication object, which can be retrieved from the Context calling its getAuthentication() method.

public interface Authentication {
    public boolean hasRole(String roleName);
    public boolean addRole(String roleName);
    public boolean revokeRole(String roleName);
    
    public Role[] getRoles();    
}

You can add a new role using addRole(), revoke exisiting roles using revokeRole() or check for a role using hasRole(). Using getRoles() you get an array of all currently set roles. If a default role is defined, this role will be initially set.

You can also query the current roles from within your XML/XSLT code using the XPath extension function pfx:hasRole(rolename)

<ixsl:if test="pfx:hasRole('MYROLE')">
  ...
</ixsl:if>

If you try to access a page for which the authconstraint isn't fulfilled, you're forwarded to the according login page. The login page has to be a regular page, e.g. containing a login form. Login forms require the type attribute set to auth.

<pfx:forminput type="auth">
  ...
</pfx:forminput>

The framework automatically inserts an authentication element into the login page's DOM tree. This element contains the state of the authenticated flag, the targetpage which should be accessed, the current roles and the authorizationfailure containing the violated authconstraint.

<formresult>
  <authentication authenticated="true" targetpage="mypage">
    <roles>
      <role name="SOMEROLE"/>
    </roles>
    <authorizationfailure authorization="pageaccess" target="mypage">
      <authconstraint>
        <hasrole name="MYROLE"/>
      </authconstraint>
    </authorizationfailure>
  </authentication>
  ...
</formresult>

You can use this information to decide which information to display on the login page.

Example 7.1. Configuring roles

The following example defines three roles. The role ANONYMOUS is configured as initial role, i.e. every session/context has this role automatically set from the beginning.

There are two top-level authconstraints. The authconstraint AC_DEFAULT is declared as default, i.e. pages, having no explicitly set authconstraint, will get this one. The authconstraint's authpage is set to login and it has a simple condition saying that it requires the role ANONYMOUS.

The authconstraint AC_KNOWN declares that it requires the USER or the ADMIN role. This authconstraint is referenced by the pagerequest userpage, using an empty authconstraint element having a ref attribute containing the authconstraint's id.

The pagerequest adminpage contains an anonymous authconstraint element, which defines the role ADMIN as requirement.

<contextxmlserver>

  <role name="ANONYMOUS" initial="true"/>
  <role name="USER"/>
  <role name="ADMIN"/>
  
  <authconstraint id="AC_DEFAULT" authpage="login" default="true">
    <hasrole name="ANONYMOUS"/>
  </authconstraint>
  
  <authconstraint id="AC_KNOWN" authpage="login">
    <or>
      <hasrole name="USER"/>
      <hasrole name="ADMIN"/>
    </or>
  </authconstraint>
  
  <pagerequest name="home">
    ...
  </pagerequest>
  
  <pagerequest name="login">
    <input>
      ...
    </input>
  </pagerequest>

  <pagerequest name="adminpage">
    <authconstraint authpage="login">
      <hasrole name="ADMIN"/>
    </authconstraint>
    ...
  </pagerequest>
  
  <pagerequest name="userpage">
    <authconstraint ref="AC_KNOWN"/>
    ...
  </pagerequest>
  ...
</contextxmlserver>

7.6.2. Custom conditions

You can extend the authentication mechanism by providing custom conditions (additionally to the predefined conditions: hasrole, and, or, not). Therefore you just have to implement the Condition interface and register your implementation class in the context configuration file. Then your custom condition can be used within authconstraints, just as the builtin conditions and arbitrarily mixed with them.

public interface Condition {
    public boolean evaluate(Context context);
}

You have to implement the evaluate method, which returns if the condition is fulfilled. Therefore the evaluation logic can access the Context. Implementations shouldn't change the data model, perform fast and hold only immutable state (or be stateless).

The following example shows a condition which retrieves a ContextResource for a customer and checks if its debit exceeds a configured limit. The limit is automatically set to a value configured as property in the condition's configuration.

package example;

import de.schlund.pfixcore.auth.Condition;
import de.schlund.pfixcore.workflow.Context;
import example.ContextCustomer;

public class PremiumCustomerCondition implements Condition {
    
    private float limit;

    public boolean evaluate(Context context) {
        ContextCustomer contextCustomer=context.getContextResourceManager().getResource(ContextCustomer.class);
        return contextCustomer.getTotalDebit() >= limit;
    }
    
    public void setLimit(float limit) {
        this.limit = limit;
    }
    
}

Let's look how this condition is registered and used in the context configuration:

<condition id="isPremiumCustomer" class="example.PremiumCustomerCondition">
  <property name="limit" value="100000"/>
</condition>
  
<authconstraint id="..." authpage="...">
  <and>
    <hasrole name="..."/>
    <condition ref="isPremiumCustomer"/>
  </and>
</authconstraint>

Conditions are registered using top-level condition elements. They require an id and a class attribute. Authconstraints can reference conditions using condition elements with an according ref attribute.

Conditions can be checked from within XML/XSL using the pfx:condition function, e.g.:

<ixsl:if test="pfx:condition('isPremiumCustomer')">
  ...
</ixsl:if>

7.6.3. Custom RoleProvider

Roles by default are configured within the context configuration. As an alternative approach you're able to plug-in your own RoleProvider implementation. Thus you can provide roles programmatically or from another source.

You just have to implement the RoleProvider interface and register the implementation class in the context configuration.

public interface RoleProvider {
    public Role getRole(String roleName) throws RoleNotFoundException;
    public List<Role> getRoles();
}

The getRole method returns a Role object by name. The getRoles method returns a list of all available roles.

public interface Role {
    public String getName();
    public boolean isInitial();
}

Role objects can either be created by implementing the Role interface or by using the default implementation de.schlund.pfixcore.auth.RoleImpl. Role implementations have to return a unique name and if they should be initially set.

The following example shows a simple RoleProvider implementation, which holds the roles in a programmatically filled map.

public class MyRoleProvider implements RoleProvider {
    private Map<String, Role> roles;

    public MyRoleProvider() {
        roles = new HashMap<String, Role>();
        Role role = new RoleImpl("ADMIN", false);
        roles.put(role.getName(), role);
    	...
    }

    public Role getRole(String roleName) throws RoleNotFoundException {
        Role role = roles.get(roleName);
        if(role == null) throw new RoleNotFoundException(roleName);
        return role;
    }

    public List<Role> getRoles() {
        return new ArrayList<Role>(roles.values());
    }
}

The RoleProvider implementation is registered using the context configuration top-level roleprovider element with a class attribute. You can optionally configure properties, which will be automatically injected into the RoleProvider instance using according setter methods (Spring bean-style setter injection).

<roleprovider class="example.MyRoleProvider">
  <property name="..." value="..."/>
</roleprovider>

You should be aware that the current mechanism doesn't support dynamic RoleProviders. The provided roles have to be constant, i.e. they're read at application startup time and aren't updated at a later time.

7.7. AJAX services

Pustefix provides AJAX-support via SOAP webservices and JSON RPC-style services. Autogenerated stubs and dynamic proxies make it easy to implement new services without any knowledge of the protocol details, among other things the protocol can be switched without changing the Javascript or Java code, additionally a service can work with both protocols at the same time.

Implementing a service can be done in two ways: Either you can export an arbitrary Spring bean as a webservice or you have to create a special service class which derives from an abstract framework class. The first way is recommended, because your service can be a POJO without any framework dependencies.

  • A managed service can be an arbitrary POJO that's managed by the Spring container. Such a Spring bean can be exported as webservice within the Spring configuration file using a special namespace (see Configuration section below).
  • An unmanaged service consists of a Java interface, which defines all remotely available service methods, and the service implementation, implementing this interface and extending the abstract service base class org.pustefixframework.webservices.AbstractService. Thus the implementation class inherits the method getContextResourceManager(), which can be used to access ContextResources. The service has to be declared explicitly in the project's central webservice configuration file projectdir/conf/webservice.conf.xml (see Configuration section below).
[Note]Note

If you want to use SOAP as service protocol, you have to note that the SOAP runtime used by Pustefix (Sun's JAXWS implementation) requires that service classes are annotated with the javax.jws.WebService annotation.

The Pustefix build process automatically generates Javascript SOAP stubs for all declared services (if the SOAP protocol is enabled). It also generates the deployment descriptors needed by Axis, the SOAP/webservices library used in Pustefix. Optionally it can create WSDL descriptions for all webservices. The JSON protocol support needs no build time processing, cause the stubs can either be dynamic proxies or generated at runtime by the server.

Using the services on the client-side is quite easy too. You just have to include the necessary Javascript libraries provided by Pustefix and instantiate a service proxy object via Javascript. The service proxy object delegates your local method calls to the server, which invokes the according methods on your service implementation and returns the result. The remote invocation works completely transparent for the client, just as a local method call.

The SOAP proxy is an autogenerated Javascript stub, which uses Pustefix's home-brewed Javascript SOAP implementation (as long as the native browser support for SOAP isn't sufficient). JSON provides two different proxy models: a dynamic proxy which is set up dynamically at runtime (during its instantiation the service's method list is requested from the server and according Javascript methods are created) and a stub which is generated at runtime by the server.

The client-server communication is done using the XmlHttpRequest object (supported by most modern browsers). Thus service requests can be done either synchronous or asynchronous (passing a callback function as parameter instead of getting the result directly from the service method). There's also a fallback mechanism (via hidden iframes) for older browsers that do not know a XmlHttpRequest object.

7.7.1. Service configuration

Spring-managed services are configured as part of the Spring configuration. You can export an arbitrary bean as webservice using the webservice element (from the http://pustefixframework.org/schema/webservices namespace). Therefor you have to reference it using the ref attribute and set a unique servicename.

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:ws="http://pustefixframework.org/schema/webservices"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
            http://pustefixframework.org/schema/webservices
            http://pustefixframework.org/schema/webservices/pustefix-webservices.xsd">

  <bean id="MyBeanId" class="mypackage.MyBean" scope="session">
    <aop:scoped-proxy/>
  </bean>
  
  <ws:webservice
    id="Webservice_MyBean" 
    servicename="MyBean" 
    interface="MyBeanServiceInterface"
    ref="MyBeanId" 
  />

  <!-- configuration with optional settings --> 
  <ws:webservice
    id="Another_Webservice"
    servicename="MyService"
    interface="MySeviceInterface"
    ref="AnotherBean"
    protocol="ANY"
    authconstraint="MyConstraint"
    synchronize="false"
    whitelist="mypackage.MyClass otherpackage.*"
  />

</beans>
      

You can optionally set an interface which defines the methods which should be exported (by default all public methods are exported, if you're using SOAP/JAXWS you can also exclude methods using the @WebMethod(exclude=true) annotation). Using the protocol attribute you can set the webservice protocol (default is JSONWS, other options are SOAP or ANY).

If you're using class hinting, all permitted classes have to be listed as value of the whitelist attribute. You can either directly list the full class names (space or comma separated), or you can use regular expressions.

The webservice configuration file PROJECTNAME/conf/webservices.conf.xml contains global settings for the runtime system, default settings for webservices, and optionally webservice-specific settings. If you're using Spring-managed services only, you can ignore the webservice-specific settings in this file as they're done as part of the Spring configuration. The global and default settings are applied to the managed services too.

<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
                      http://www.pustefix-framework.org/2008/namespace/webservice-config.xsd">

  <!--
    The webservice-global section contains general configuration options, which are applied to
    all webservices. Some of the options can be overridden individually for single webservices.
  -->
  <webservice-global>

    <!--
      Set if WSDL should be generated for SOAP-enabled webservices and where to store it (absolute path within
      the project's webapp directory)
      
      [Optional - Default: enabled="true" repository="/wsdl"]
    -->
    <wsdlsupport enabled="true" repository="/wsdl"/>

    <!--
      Set if Javascript stubs should be generated and where to store it (absolute path within the
      project's webapp directory - only for SOAP). The jsnamespace attribute controls what namespace
      prefix the generated Javascript should use (COMPAT: WS_, COMPAT_UNIQUE: WS_ for SOAP JWS_ for JSON,
      JAVA_NAME: full Java class name, other values are used as namespace themselves, dot-separated
      namespaces are supported too, code to setup the according JS context objects gets auto-generated)
      
      [Optional - Default: enabled="true" repository="/wsscript" jsnamespace="COMPAT"]
    -->
    <stubgeneration enabled="true" repository="/wsscript" jsnamespace="COMPAT"/>

    <!--
      Set which service protocol should be used. You can set a certain protocol (SOAP|JSONWS) or allow all
      protocols (ANY). If you choose to use JSONWS only, you should set the type to JSONWS to accelerate
      the build process, otherwise the SOAP stubs will be generated unnecessarily.
      
      [Optional - Default: type="JSONWS"]
    -->
    <protocol type="ANY"/>

    <!--
      Set which SOAP encoding style should be used. The best supported style is rpc/encoded.
      The server-side also supports other styles like rpc/literal and document/literal, but the client-side
      support for these styles is rudimentary.
      
      [Optional - Default: style="rpc" use="encoded"]
    -->
    <encoding style="rpc" use="encoded"/>

    <!--
      Set if the JSON representation of serialized Java beans should be augmented with
      type meta information (classhinting).
      
      [Optional - Default: classhinting="false"]
    -->
    <json classhinting="true"/>

    <!--
      Set if webservice requests need to have a valid HTTP/Pustefix-Session (servlet) or they should
      work without one too (none).
      
      [Optional - Default: type="servlet"]
    -->
    <session type="servlet"/>

    <!--
      Set the service object's scope. Scope application means that the service object is created only
      once and shared between all sessions, session, that it's created once per session, request, that
      it's created newly for each request.
      
      [Optional - Default: type="application"]
    -->
    <scope type="application"/>

    <!--
      Set if only https requests should be allowed.
      
      [Optional - Default: force="false"]
    -->
    <ssl force="true"/>

    <!--
      Configure if service requests should be synchronized on the Pustefix Context (default is true).
      [Optional - Default: synchronize="true"]
    -->
    <context synchronize="true"/>


    <!--
      There are some options which should be configured differently in development and production mode.
      Therefor you can use the following choose/when elements (which are optional, you can just leave them out).
    -->
    <choose>
      <when test="$mode = 'prod'">
        <!--
          Set if admin tool should be available (see Development Tools).
          
          [Optional - Default: enabled="false"]
        -->
        <admin enabled="false"/>

        <!--
          Set if monitor tool should be available (see Development Tools).
          
          [Optional - Default: enabled="false"]
        -->
        <monitoring enabled="false"/>

        <!--
          Set if extensive logging should be enabled (i.e. logging of all request/response messages in
          pustefix-webservice.log).
          
          [Optional - Default: enabled="false"]
        -->
        <logging enabled="false"/>

        <!--
          Set a FaultHandler, which will process exceptions before they are sent to the client
          (see Exception Handling).
          
          [Optional - Default: none]
        -->
        <faulthandler class="de.schlund.pfixcore.webservice.fault.EmailNotifyingHandler">
          <param name="recipients" value="errors@domain.de"/>
          <param name="sender" value="pfxerror@domain.de"/>
          <param name="smtphost" value="localhost"/>
        </faulthandler>
      </when>
      <otherwise>
        <admin enabled="true"/>
        <monitoring enabled="true" scope="session" historysize="10"/>
        <logging enabled="true"/>
        <faulthandler class="de.schlund.pfixcore.webservice.fault.LoggingHandler"/>
      </otherwise>
    </choose>
  </webservice-global>

  <!--
    Each service has to define name, interface and implementation. All other options are
    optional and inherited from the global section respectively. Some global options
    can be overridden here (stubgeneration, protocol, encoding, json, session, scope,
    ssl, context, faulthandler).
  -->

  <webservice name="Counter">
  
    <!--
      Set the service interface. [Mandatory]
    -->
    <interface name="de.schlund.pfixcore.example.webservices.Counter"/>

    <!--
      Set the service implementation class. [Mandatory]
    -->
    <implementation name="de.schlund.pfixcore.example.webservices.CounterImpl"/>

    <!--
      List which classes are permitted to be deserialized using class hinting
    -->
    <whitelist>mypackage.MyClass otherpackage.*</whitelist>
  </webservice>
  <webservice name="...">...</webservice>...
</webservice-config>

7.7.2. Exception handling

Pustefix provides a special exception handling mechanism for AJAX services. You can register predefined or custom FaultHandlers, which are automatically called before an exception is sent to the client. Thus you can filter exceptions, change them or do some logging or notification stuff.

There are three predefined FaultHandlers:

  • de.schlund.pfixcore.webservice.fault.LoggingHandler: as its name denotes, it just logs all Exceptions via log4j (location can be configured within the general log4j configuration)
  • de.schlund.pfixcore.webservice.fault.EmailNotifyingHandler: this handler sends Email notifications (recipients, sender and smtphost can be configured as parameters, see the Configuration section)
  • de.schlund.pfixcore.webservice.fault.ExceptionProcessorAdapter: this handler implements a direct connection to the general Pustefix exception processing mechanism via ExceptionProcessors (and only makes sense if the configured ExceptionProcessor only consumes exceptions, but doesn't produce any output, like a HTML error page)

You are free to implement your own FaultHandler and register it with your services in the webservice configuration file. You just have to extend the abstract base class de.schlund.pfixcore.webservice.fault.FaultHandler and implement the abstract methods init() and handleFault(Fault). The Fault object methods getThrowable() and setThrowable can be used to get the thrown exception and to change or replace it.

You also can derive from one of the predefined handlers. E.g. you can extend the EmailNotifyingHandler by overriding the two methods public boolean isInternalServerError(Fault fault) and public boolean isNotificationError(Fault fault) to customize if error details should be hidden from the client and if a notification mail should be sent for certain exceptions.

7.7.3. Development tools

Pustefix provides some tools, which can help you during the development process. It provides an admin and monitoring webinterface. The admin tool lists all registered services with their service methods. The monitoring tool shows a history of the last requests including the request and response messages.

You can access these tools by including the special tag <pfx:webserviceconsole/> (see Section 5.6.4, “Using the Pustefix console”) into your Pustefix page and enabling them in the global webservice configuration (as shown in the Configuration section: the admin and monitoring elements within the choose/when elements).

By the way, if any unexplainable problems occur, you're recommended to take a look into the logfile pustefix-webservice.log, where (if log4j log level is set to DEBUG) among other things the request and response messages are logged too.

7.7.4. Callback mechanisms

Doing asynchronous webservice calls, you can choose between two different webservice callback mechanisms.

Callback functions

You can pass a function reference as last argument of the service method call (or last but one, if you want to set a request ID too). After receiving the server's response your function will be automatically called, passing the result (if no exception occurred), the request ID (if set) and an Error object (if exception occurred).

var service=new WS_Service();
var callback=function(result,requestid,exception) {
   if(exception) {
      ...
   } else {
      ...
   }
}
var requestid="...";
service.serviceMethod(arg0, arg1, ... , callback); //asynchronous with callback
service.serviceMethod(arg0, arg1, ... , callback, requestid); //asynchronous with callback and requestid

Callback objects

You can instantiate the webservice stub with an object reference as argument of the constructor function. Your object has to provide methods of the same name as the service methods which are automatically called back by the stub (you don't have to pass a callback object or function to the serviceMethod).

var object={
   serviceMethod: function(result,requestid,exception) {
      if(exception) {
         ...
      } else {
         ...
      }
   }
};
var service=new WS_Service(object); //instantiate service with callback object 
//optionally you can pass the scope object within 
var requestid="...";
service.serviceMethod(arg0, arg1, ...); //asynchronous
service.serviceMethod(arg0, arg1, ... , requestid); //asynchronous with requestid

If you're using JSON stubs, you can optionally pass a scope object as second constructor argument. Thus this scope is used as the "this" context argument instead of the object itself when the object's callback method is called. [Since: 0.13.1]

        var service = new WS_Service(object, scope);

7.7.5. Type mapping

Supported Java types are:

  • Primitive Java types (boolean, byte, short, int, long, float, double)
  • Object wrappers for primitive types (Boolean, Byte, Short, Integer, Long, Float, Double from java.lang)
  • String type (java.lang.String)
  • Date types (java.util.Calendar, java.util.Date)
  • Java beans (nested arbitrarily)
  • Arrays of all other supported types (except for java.util.Date with SOAP), including multidimensional arrays

Primitive, wrapper, String and Date types are mapped to their according Javascript counterpart (Number, Boolean, String, Date). Java beans are mapped to general Javascript objects with the according properties (there's no representation/simulation of the Java bean's type hierarchy, using JSON you optionally can enable classhinting, which provides every object instance with a special property - javaClass - containing the full Java class name).

The Java bean mapping mechanism supports both, so-called simple properties (having according getter and setter methods as defined in the Java Bean Specification) and public members (without access methods).

Additional Java types supported by JSON services:

  • Primitive and object wrapper type for characters (char and java.lang.Character)
  • Arbitrary Lists (Java -> JSON), parameterized Lists with type parameter of instantiable type (JSON -> Java)
  • Arbitrary Maps with keys of type java.lang.String (Java -> JSON), parameterized Maps with key type parameter of type java.lang.String and value type parameter of instantiable type (JSON -> Java)

Because of the restrictions of the JSON format, especially the absence of type information and the usage of builtin Javascript types (arrays for Lists, objects for Maps), the support for Lists and Maps itself is restricted: deserializing Lists to Java requires a parameterized type parameter and this type has to be instantiable (or be a parameterized List or Map itself), the same with Map values, Map keys have to be java.lang.String instances.

Custom bean mapping via Java annotations

The JSON bean (de-)serialization mechanism supports customizable bean property mappings via Java annotations. You can exclude individual properties from (de-)serialization by marking the according getter with an @Exclude annotation or you can exclude all properties by marking the bean class with an @ExcludeByDefault annotation and include individual properties with @Include annotations at their getters (marking public members is supported too).

The following examples show the different annotations in action. Both class definitions give access to the foo and baz property and restrict access to the bar property. The first class definition includes all properties by default (which is the default behaviour without a type annotation) and excludes the bar property, while the second excludes all properties by default and includes the foo and baz properties:

public class A {

    int foo;
    int bar;
    int baz;

    public int getFoo() {return foo;}
    public void setFoo(int foo) {this.foo=foo;}

    @Exclude
    public int getBar() {return bar;}
    public void setBar(int bar) {this.bar=bar;}

    public int getBaz() {return baz;}
    public void setBaz(int baz) {this.baz=baz;}

}
@ExcludeByDefault
public class A {

    int foo;
    int bar;
    int baz;

    @Include
    public int getFoo() {return foo;}
    public void setFoo(int foo) {this.foo=foo;}

    public int getBar() {return bar;}
    public void setBar(int bar) {this.bar=bar;}

    @Include
    public int getBaz() {return baz;}
    public void setBaz(int baz) {this.baz=baz;}                                                                                                           }

}

All annotations only take effect in the declaring class and aren't inherited. Thus, if you derive your bean class, the type annotation of your base class has no effect, so declaring new properties in your class will include them by default. Properties included or excluded in your base class will be included/excluded in your inherited class too, if you don't redeclare or override this properties. The following example demonstrates this behaviour:

public class A {

    int foo;
    int bar;
    int baz;

    public int getFoo() {return foo;}
    public void setFoo(int foo) {this.foo=foo;}

    @Exclude
    public int getBar() {return bar;}
    public void setBar(int bar) {this.bar=bar;}

    public int getBaz() {return baz;}
    public void setBaz(int baz) {this.baz=baz;}
}

@ExcludeByDefault
public class B extends A {

    int hey;
    int ho;

    @Include
    public int getHey() {return hey;}
    public void setHey(int hey) {this.hey=hey;}

    public int getHo() {return ho;}
    public void setHo(int ho) {this.ho=ho;}

    @Override
    public int getBaz() {return super.getBaz();}

}

Using the @Alias annotation you can control the name used for (de-)serialization (i.e. the name used as JSON property name). The following example shows how to add aliases to a public member and a property getter.

public class A {

    @Alias("mybaz")
    public int baz;
    int foo;

    @Alias("myfoo")
    public int getFoo() {return foo;}
    public void setFoo(int foo) {this.foo=foo;}

}

If you don't like Java annotations or you want to overwrite existing annotations, you can also customize your beans using a XML configuration file. The file has to be named beanmetadata.xml and has to be placed in the project configuration directory or in a META-INF directory that's part of the classpath.

<bean-metadata xsi:schemaLocation="http://pustefix.sourceforge.net/bean-metadata 
                                   http://pustefix.sourceforge.net/beanmetadata.xsd">
  <bean class="de.schlund.pfixcore.webservice.beans.A">
    <property name="foo" alias="myfoo"/>
    <property name="bar" exclude="true"/>
  </bean>
  <bean class="de.schlund.pfixcore.webservice.beans.B" exclude-by-default="true">
    <property name="hey"/>
  </bean>
</bean-metadata>

The format is very simple: Create a bean element referencing the class you want to annotate. Add property elements referencing the properties you want to annotate. All Java annotations have an according XML attribute counterpart you can add to these elements.

7.8. Object-to-XML mapping

Pustefix provides a lightweight object serialization mechanism, which can be used to serialize arbitrary objects into the result DOM without having to do any DOM operations by yourself. The XML binding is customizable via Java annotations within the bean classes.

The framework supports arbitrary Beans, Arrays, Collections, Maps, Numbers (including the primitive types and their object wrapper types), Strings, and Date/Calendar. To support other types or to serialize to a custom format, it's possible to write your own serializers and annotations (to attach them to the according bean properties).

The serialization of beans can be customized using the generic Pustefix bean annotations, which are known from the JSON serialization framework. You can exclude individual properties from serialization by marking the according getter with an @Exclude annotation or you can exclude all properties by marking the bean class with an @ExcludeByDefault annotation and include individual properties with @Include annotations at their getters (marking public members is supported too). Using the @Alias annotation you can control the name used as the resulting attribute or element name.

The serialization to the result tree is done by calling one of the static addObject methods of the ResultDocument class. The element argument is the parent DOM element at which the serialized XML will be appended, the optional name argument can be used to create an additional child element for the serialized XML. The object argument is the object, which should be serialized.

public class ResultDocument {
    ...
    public static Element addObject(Element element, Object object) {...}
    public static Element addObject(Element element, String name, Object object) {...}
}

7.8.1. Serialization process

The default serialization process tries to produce relatively compact XML. Thus it favours attributes over elements and serializes so-called simple types, which can be represented as strings, into attributes where it's possible and makes sense, e.g. for bean properties.

Let's look at an example, which shows the serialization of a simple bean using bean and serializer annotations to customize the serialization behaviour:

...
	  
public class Account {
  
    private long accountNo;
    private float debit;
    ...
  
    public long getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(long accountNo) {
        this.accountNo = accountNo;
    }
  
    @Alias("balance")
    public float getDebit() {
        return debit;
    }
  
    public void setDebit(float debit) {
        this.debit = debit;
    }
  
    public Currency getCurrency() {
        return currency;
    }
  
    public void setCurrency(Currency currency) {
        this.currency = currency;
    }
  
    @DateSerializer("yyyy-MM-dd HH:mm:ss")
    public Calendar getOpeningDate() {
        return openingDate;
    }
  
    public void setOpeningDate(Calendar openingDate) {
        this.openingDate = openingDate;
    }
  
    @Exclude
    public String getComment() {
        return comment;
    }
  
    public void setComment(String comment) {
        this.comment = comment;
    }
}

Here you see how the bean's serialized to the ResultDocument within a ContextResource:

...
        
public class ContextAccountImpl implements ContextAccount {

    private Account account;
    ...

    public void insertStatus(ResultDocument resdoc, Element elem) throws Exception {
        ResultDocument.addObject(elem,"account",account);
    }    
}

The resulting DOM fragment looks like this:

<formresult serial="1199439160721">
  ...
  <data>
    <account accountNo="2000123" currency="EUR" balance="332.54" openingDate="2003-11-04 09:15:38"/>
  </data>
  ...
</formresult>

The data element is the ContextResource's root node as configured in the configuration file. Calling addObject with the additional account argument, the serialized bean isn't added directly to the data element, but an additional element is used. The bean's properties are serialized as attributes of this element.

The debit property is renamed to balance using the @Alias annotation. The comment property is excluded using the @Exclude annotation. The openingDate property is serialized using the built-in DateSerializer, which can be customized using the @DateSerializer annotation. Thus you can provide your own date format pattern (must be a pattern supported by java.text.SimpleDateFormat).

Only simple type properties, i.e. properties which can be serialized to string values, can be represented as attributes. If the Account bean would have an additional property customer of a bean type, e.g. a Customer class, this property would be serialized as a child element:

<formresult serial="1199439160721">
  ...
  <data>
    <account accountNo="2000123" balance="EUR" debit="332.54" openingDate="2003-11-04 09:15:38">
      <customer customerId="100000" firstName="Mike" lastName="Foo"/>
    </account>
  </data>
  ...
</formresult>

Collections and Arrays are represented using an element for each entry. The element name is derived from the the simple name of the entry's class (without package name and starting lowercase):

<formresult serial="1199439160721">
  ...
  <data>
    <account accountNo="2000000" currency="EUR" balance="3124.49" openingDate="2003-10-23 08:05:10"/>
    <account accountNo="2000123" currency="EUR" balance="332.54" openingDate="2003-11-04 09:15:38"/>
    <account accountNo="2001405" currency="EUR" balance="25123.11" openingDate="2005-01-13 10:10:10"/>
  </data>
  ...
</formresult>

The element name can be changed using the @ClassNameAlias annotation, e.g. to rename the account element to bankaccount:

@ClassNameAlias("bankaccount")
public class Account {
    ...
}

Maps are represented using an entry element for each map entry. Key and value are represented by child elements (whereas the element names are derived from the class names):

<formresult serial="1199439160721">
  ...
  <data>
    <entry>
      <long>2000000</long>
      <account accountNo="2000000" currency="EUR" debit="3124.49" openingDate="2003-10-23 08:05:34"/>
    </entry>
    <entry>
      <key>2001405</key>
      <account accountNo="2001405" currency="EUR" debit="25123.11" openingDate="2005-01-13 10:10:34"/>
    </entry>
    <entry>
      <key>2000123</key>
      <account accountNo="2000123" currency="EUR" debit="332.54" openingDate="2003-11-04 09:15:34"/>
    </entry>
  </data>
  ...
</formresult>
[Tip]Changing the tag name for map entries

The tag name, that is used for the entries in the map can be changed using the @MapSerializer (see the section called “@MapSerializer”).

Circular object references are handled by adding a xpathref attribute to the according element. Its value is an absolute XPath expression referencing the according object's element:

<formresult serial="1199702214819">
  ...
  <data>
    <account accountNo="2000123">
      <customer customerId="100000">
        <accounts>
          <account accountNo="2000000">
            <customer xpathref="/formresult/data[1]/account[1]/customer[1]"/>
          </account>
          <account xpathref="/formresult/data[1]/account[1]"/>
          <account accountNo="2001405">
            <customer xpathref="/formresult/data[1]/account[1]/customer[1]"/>
          </account>
        </accounts>
      </customer>
    </account>
  </data>
  ...
</formresult>

In this example the Account bean has a reference to a Customer bean, which itself has a reference to all of its Accounts. You can see that all beans, which were already serialized (as ancestors in the tree) contain an according back-reference.

7.8.2. Built-in serializers

Pustefix already provides several XML serializers for common serialization tasks.

Simple serializers

Simple serializers serialize scalar values (like strings, numbers or booleans). They are added as a new attribute on the current tag.

@DateSerializer

The @DateSerializer is used to select the date format when serializing Date or Calendar objects.

Complex serializers

Complex serializers are used to serialize complex data structures. These always result in new tags that are being added to the document.

@ForceElementSerializer

The @ForceElementSerializer will create an XML tag for primitive values instead of writing the value to an XML attribute.

It can be combined with any simple type serializer (see the section called “Simple serializers”.

@CDataSerializer

The @CDataSerializer will create a CData section in the resulting XML document.

@MapSerializer

The @MapSerializer is used to serialize instances of java.util.Map.

@XMLFragmentSerializer

Strings that contain XML code can be inserted as XML fragment the the resulting document by using the @XMLFragmentSerializer:

public class FragmentBean {
      
    private String myFragment = "<foo><bar baz=\"true\"/>character data</foo>";

    @XMLFragmentSerializer
    public String getMyFragment() {
        return myFragment;
    }
}

The XML, that is returned by the getMyFragment method is not treated as a simple string, but as an XML fragment and thus, the content is not escaped, when inserted in the document:

<?xml version="1.0" encoding="utf-8"?>
<result>
  <myFragment>
    <foo><bar baz="true"/>character data</foo>
  </myFragment>
</result>

7.8.3. Custom serializers

If you don't like the default serialization mechanism or you use unsupported types, you can write your own serializers. There are two types of serializers: SimpleTypeSerializers, which can produce String values (e.g. for primitive types), and ComplexTypeSerializers, which can produce structured XML data (e.g. for bean types).

Implementing your own serializer just requires to implement the SimpleTypeSerializer or ComplexTypeSerializer interface and create a custom annotation to be able to attach your serializer to a bean property.

Let's look at an example of a SimpleTypeSerializer: a custom String serializer, which allows to configure if Strings should be ouput lower- or uppercase. Here's the implementation:

...
import de.schlund.pfixcore.oxm.impl.AnnotationAware;
import de.schlund.pfixcore.oxm.impl.SimpleTypeSerializer;
import de.schlund.pfixcore.oxm.impl.annotation.StringSerializer;
...

public class StringTypeSerializer implements SimpleTypeSerializer, AnnotationAware {

    private boolean doLowerCase;

    public void setAnnotation(Annotation annotation) {
        StringSerializer s=(StringSerializer)annotation;
        doLowerCase=s.value();
    }

    public String serialize(Object obj, SerializationContext context) throws SerializationException {
        if(obj instanceof String) {
          String str=(String)obj;
          if(doLowerCase) str=str.toLowerCase();
          else str=str.toUpperCase();
          return str;
        } 
        throw new SerializationException("Illegal type: "+obj.getClass().getName());
    }
}

The serializer implements the SimpleTypeSerializer interface. Its serialize method checks if the passed object is of type String and calls toLowerCase or toUpperCase before returning the new String. The doLowerCase property controls which method is used. This property is set within the setAnnotation method. The method is defined in the AnnotationAware interface. This method is called by the framework after the serializer is instantiated and passes the annotation set at the according bean property. So you can access the configured values and configure your serializer. Let's look at the according annotation definition:

...
        
@SimpleTypeSerializerClass(StringTypeSerializer.class)
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface StringSerializer {
    boolean value();
}

You have to annotate the custom annotation with a SimpleTypeSerializerClass annotation with the serializer class as value, make the annotation available to methods and fields using the @Target annotation and make it visible at runtime using the @Retention annotation. The rest of the annotation definition can be done according to your needs. In the example we just define a boolean property indicating if the String should be converted to lower- or uppercase. Here you see how the annotation is applied to serialize a customer's lastname as uppercase:

public class Customer {
    ...
    @StringSerializer(false)
    public String getLastName() {...}
}

Let's look at an example of a ComplexTypeSerializer. We want to customize the serialization of a Customer bean: the firstName and lastName properties should be output together within a name element:

public class Customer {
    ...
    public long getCustomerId() {...}
    public String getFirsstName() {...}
    public String getLastName() {...}
    public List<Account> getAccounts() {...}
    ...
}

The serializer just implements ComplexTypeSerializer. We don't need to implement AnnotationAware because our annotation will have no parameter we may want to read:

public class CustomerTypeSerializer implements ComplexTypeSerializer {

    public void serialize(Object obj, SerializationContext context, XMLWriter writer) throws SerializationException {
        if(obj instanceof Customer) {
            Customer customer=(Customer)obj;
            writer.writeStartElement("name");
            writer.writeCharacters(customer.getFirstName()+" "+customer.getLastName());
            writer.writeEndElement("name");
            context.serialize(customer.getAccounts(),writer);
        } else {
            throw new SerializationException("Illegal type: "+obj.getClass().getName());
        }
    }
}

The serialize method gets a XMLWriter object, which is used to write the name element. Then the passed SerializationContext is used to serialize the customer's accounts using the default serialization mechanism.

Finally we implement a custom annotation:

@ComplexTypeSerializerClass(de.schlund.pfixcore.example.bank.oxm.CustomerTypeSerializer.class)
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomerSerializer {}

Here we apply the annotation to the Account bean's customer property:

public class Account {
    ...
    @CustomerSerializer
    public Customer getCustomer() {...}
    ...
}

Here's an excerpt of the resulting XML:

<formresult serial="1199705488403">
  ...
  <data>
    <account accountNo="2000000" balance="3124.49" currency="EUR" openingDate="2003-10-23 08:05:22">
      <customer>
        <name>Mike Foo</name>
        <account accountNo="..."/>
        <account accountNo="..."/>
        ...
      </customer>
    </account>
  </data>
  ...
</formresult>

7.8.4. Using JAXB serialization

As an alternative to the builtin XML marshalling you can use JAXB. Pustefix checks if the class definition of an object is annotated with the JAXB annotation javax.xml.bind.annotation.XmlRootElement and, if so, hands over the serialization to JAXB.

...
	 
@XmlRootElement 
public class Account {
  
    private long accountNo;
  
    @XmlAttribute
    public long getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(long accountNo) {
        this.accountNo = accountNo;
    }

    ...

}

7.9. Annotation-based IWrapper creation

The annotation-based IWrapper creation provides an alternative to the usual, XML configuration based, IWrapper creation. Using this approach you create IWrappers from standard Java Beans by adding the necessary configuration data in the form of annotations.

The IWrappers are automatically created during the build process using the Sun JVM's apt tool and a custom AnnotationProcessor which analyzes the Java bean's source code and generates the according IWrapper sources.

You're making a bean to a template for an IWrapper by adding an @IWrapper annotation to its class declaration. By default every bean property that is of a so-called builtin type, i.e. has a pre-defined IWrapperParamCaster implementation, will be automatically added as an IWrapper parameter.

Builtin types are boolean, byte, double, float, int, long, java.lang.Boolean, java.lang.Byte, java.lang.Double, java.lang.Float, java.lang.Integer, java.lang.Long, java.lang.String, java.util.Date and Arrays with components of these types.

Bean properties of an unknown type are either ignored or require a @Caster annotation specifying an appropriate caster. Bean properties can be annotated at their getter methods or at the field itself, if it's public. If a property of a builtin type should be skipped you can mark the according property with a @Transient annotation.

Bean based IWrappers can be used to create new beans or fill existing beans with the IWrapper's state. Therefore the IWrapperToBean class provides the two static methods <T> T createBean(IWrapper wrapper, Class<T> beanClass) and populateBean(IWrapper wrapper, Object obj).

7.9.1. IWrapper annotations

Every IWrapper configuration element known from the XML configuration has an annotation counterpart. Besides there are some special annotations like @IWrapper and @Transient. In the following we'll give a short overview of all avaible annotations:

        @Target(ElementType.TYPE)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface IWrapper {
          String name() default "";
          Class<? extends IHandler> ihandler() default IHandler.class;
          String beanRef() default "";
        }
        
        @IWrapper(name="MyBeanWrapper",ihandler=MyBeanHandler.class)
        public class MyBean {
          ...
        }
      

The @IWrapper annotation is used to mark a class as template for an IWrapper. The name attribute denotes the class name of the generated IWrapper class (without package). By default the bean name with the suffix Wrapper is used (and the same package). The ihandler attribute denotes the IHandler implementation class. Alternatively you can use the beanRef attribute to reference a Spring managed IHandler bean by name.

        @Target({ElementType.METHOD,ElementType.FIELD})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface Param {
          String name() default "";
          boolean mandatory() default true;
          boolean trim() default true;
          String missingscode() default "";
          String[] defaults() default {};
        }
        
        @IWrapper(name="MyBeanWrapper",ihandler=MyBeanHandler.class)
        public class MyBean {
          ...
          @Param(name="MyValue",mandatory=false)
          public int getValue() {...}
        }
      

The @Param annotation is used to mark a bean property as parameter and configure its name and all the other options known from the IWrapper XML configuration. This annotation is optional, leaving it out, the property name is used as name and the other attributes are set to their default values.

        @Target({ElementType.METHOD,ElementType.FIELD})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface Caster {
          Class<? extends IWrapperParamCaster> type();
          Property[] properties() default {};
        }
        
        @IWrapper(name="MyBeanWrapper",ihandler=MyBeanHandler.class)
        public class MyBean {
          ...
          @Caster(type=SomeClassCaster.class)
          public SomeClass getValue() {...}
        }
      

The @Caster annotation denotes the caster implementation class. The nested properties attribute can be used to set properties via @Property annotations. That's the same as the cparam elements in the XML configuration (the params/properties are set using according methods prefixed with put_).

        @Target({ElementType.METHOD,ElementType.FIELD})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface Property {
          String name();
          String value();
        }
      

The @Property annotation is used as nested annotation within the properties array attribute of various annotations. It consists of simple name/value pairs.

        @Target({ElementType.METHOD,ElementType.FIELD})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface PreCheck {
          Class<? extends IWrapperParamPreCheck> type();
          Property[] properties() default {};
        }
        
        @IWrapper(name="MyBeanWrapper",ihandler=MyBeanHandler.class)
        public class MyBean {
          ...
          @PreCheck(
            type=de.schlund.pfixcore.generator.prechecks.RegexpCheck.class,
            properties={
              @Property(name="regexp",value="/^(M|L|XL)$/")
            }
          )
          public String getValue() {...}
        }
      

The @PreCheck annotation denotes the precheck implementation class with optional properties/parameters.

        @Target({ElementType.METHOD,ElementType.FIELD})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface PostCheck {
          Class<? extends IWrapperParamPostCheck> type();
          Property[] properties() default {};
        }
        
        @IWrapper(name="MyBeanWrapper",ihandler=MyBeanHandler.class)
        public class MyBean {
          ...
          @PostCheck(
            type=de.schlund.pfixcore.generator.postchecks.IntegerRange.class,
            properties={
              @Property(name="range",value="0:2")
            }
          )
          public int getValue() {...}
        }
      

The @PostCheck annotation denotes the postcheck implementation class with optional properties/parameters.

        @Target({ElementType.METHOD,ElementType.FIELD})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface Transient {}
        
        @IWrapper(name="MyBeanWrapper",ihandler=MyBeanHandler.class)
        public class MyBean {
          ...
          @Transient
          public int getValue() {...}
        }
      

The @Transient annotation can be used to avoid that a bean property of a builtin type is made to an IWrapper parameter.

7.10. Scripted workflows

Scripted flows are script files written in XML that can be used to control a user session. The script file has the following form:

<scriptedflow version="1.0"
  xmlns="http://pustefix.oss.schlund.de/scriptedflow200602">
  <!--
    The instructions are placed here.
  -->
  </scriptedflow>

7.10.1. Parameters and Variables

A scripted flow is started by using the special __scriptedflow=[FLOWNAME] parameter in the query string of a request URI. In this query string additonal parameters can be given which are then made available inside the script.

Variables

Variables can be used to store character data between steps within a scripted flow. A variable is set using the <set-variable name="variablename">content</set-variable> command. The content may consist of character data as well as the special <value-of select="<XPath-Expression>"/> command. Within XPath expressions variables can be referenced using $variablename.

Parameters

Parameters are set when a scripted flow is started. If the query string contains e.g. "name=value" the value can be accessed from within XPath expressions using the special variable name $__param_name$. Parameters can not be overwritten from within the script and never change during the execution.

Special variables

There is a special variable called $__pagename$ which contains the name of the page that would have been sent to the browser, if the last request was done directly by the browser. You can use this variable to check on which page you are at the moment. This variable cannot be written to by the script.

7.10.2. Statements

Scripted Flows are programmed using assorted statements which are explained here:

<interactive-request>

Gives control back to the user. The output page is rendered based on the current ResultDocument (which was generated by the last request) and sent to the browser. When the browser sends the next request (e.g. by sending a form or clicking on a link) it is processed as usual but instead of returning the resulting document directly to the browser the active scripted flow is continued right at the location it was suspended, using the new ResultDocument as the base for further processing.

<interactive-request>
  <param name="thename">static text combined with <value-of select="$thevar"/> calculated text (if needed)</param>
  <param name="otherparam">other text</param>
</interactive-request>

This statement can take an arbitrary number of param tags as children. These tags can be used to provide values to pre-fill html form input elements.

<virtual-request>

Triggers a virtual request which is processed by the context like a user request. This statement can have an optional attribute page which specifies the name of the page the request should be sent to. If omitted, the current page (as defined by the Context) is used. This statement can take an arbitrary number of param tags below itself. These tags can be used to provide arguments to the request (e.g. to simulate submitted form data). The tag follows the scheme:

<virtual-request page="thepage" dointeractive="false|true|reuse">
  <param name="thename">static text combined with <value-of select="$thevar"/> calculated text (if needed)</param>
  <param name="otherparam">other text</param>
</virtual-request>

The dointeractive attribute is used in case the virtual request returns an error. If set to "true" or "reuse", the system will automatically fall back to do an interactive request (the default is "false" which will just go on with the scripted flow ignoring any errors). All errors will be cleared from the document used in the interactive request. The difference between "true" and "reuse" is that in the latter case, the param tags given to the virtual request will be reused in the automatic interactive request, providing for pre-filled input fields.

Please note that the __sendingdata or __sendingauthdata parameter will not be added automatically but has to be added by the script developer, if needed. The ResultDocument returned by this request is used as the basis for further processing (e.g. XPath testing).

<set-variable>

Sets the value of a variable. As the param tag within the virtual-request tag this tag can contain text data combined with the value-of tag. See Parameters & Variables for details on how to use variables.

<if>

Allows conditional execution of a code block. Based on a XPath expression which is given using the test attribute the VM decides whether to execute the statements given below the <if> tag or not. The XPath expression can test for variables and parameters as well as querying the ResultDocument returned by the last request.

<choose>

This is the multi-branch equivalent to <if>. The form is

<choose>
  <when test="...">
    <!-- ... -->
  </when>
  <when test="...">
    <!-- ... -->
  </when>
  <otherwise>
    <!-- ... -->
  </otherwise>
</choose>

The <otherwise> branch is optional and only executed if none of the <when> conditions is met. Only the first <when> branch whose condition is met is executed, branches following this branch are not evaluated regardless of their condition.

<while>

Loops as long as condition is true. Like the <if> statement this one takes a test attribute specifying a XPath expression. When the condition is true, the code block below this tag is executed, otherwise the next statement after the <while> statement is executed. After executing the block the condition is rechecked, so the block is executed repeatedly until the condition becomes false.

<break>

Jumps out of a <while> loop. This statement is allowed anywhere below a <while> statement. It can be used to quit the loop immediately and to go on with the next statement after the loop.

<exit>

Quits the current scripted flow, returning the current ResultDocument to the browser.

7.11. Scripting Langauge support

Consider these questions:

  • Do you miss fast-prototyping in Pustefix?
  • Do you think dynamic programming languages (sometimes called scripting languages) can do more than just interpreting one liners or providing a new form of poetry?

If you answered any of the questions above with yes ... Then this is for you!

This feature brings scripting support to IHandlers and States. It's possible to develop any of them in any Bean Scripting Framework supported language, by providing a script file's location inside any IWrapper definition file or inside a pagerequest's definition. Script files can be located in either the application's docroot or the classpath.

7.11.1. IHandler

Instead of providing a class name inside an IWrapper's IHander definition like this:

<ihandler class="de.schlund.pfixcore.example.TShirtHandler"/>

you can define that a file containing dynamic language code should be used as an IHandler. It's done like this:

<ihandler class="script:sample1/script/ScriptingTShirt.js"/>

... that's it! (see below under "Path Definitions" on details about difference between scripts under docroot and scripts placed in classpath).

Simply create a file containing the dynamic language code inside the docroot or the classpath.

7.11.2. State

If you want to script a State, then simply use the above definition format inside a pagerequest's state definition, like in the following example:

<pagerequest name="scriptingstate">
  <state class="script:sample1/script/ScriptingState?.bsh"/>
</pagerequest>

7.11.3. Implementation Details

The following rules have to be considered, when scripting IHandlers and States alike.

  • You must define all methods of the respective interfaces (de.schlund.pfixcore.generator.IHandler and/or de.schlund.pfixcore.workflow.State) as functions inside your scripts.
  • The functions inside your scripts have to return the correct type, if they return values at all. Note that for example en empty String is a valid boolean expression in Javascript.
  • The file suffix must be one that's recognized by BSF. The listing of current supported language mappings of BSF helps you find the correct suffix for your used scripting lanuage. Basically they're the common suffixes like js for javascript, bsh for Beanshell and py for python.

State

Because the StateFactory caches and reuses State-instances across sessions and requests, you should be aware that script-wide global variables inside your scripts will be available to all requests and session.

This is basically the same behaviour as for States implemented in Java.

Path Definitions

There are two alternatives for defining the location of your script file.

  • If you prepend a / (slash character) to the location, the script file is searched for in the classpath of your application. No magic (or great effort) is applied when searching for the script file. Instead simply ScriptingIHandler.getClass().getResourceAsStream() is called to get the source of the script file.
  • If the script: String is not followed by a slash, the string is assumed to be a file's location relative to the docroot of the running pustefix application. This usually means everything downwards from the projects-folder.

Libraries

Of course the Java platform can't interpret every arbitrary on it's own. Special Language libraries must be available to the application in order to execute script code in the respective languages.

Pfixcore comes with the Mozilla's Javascript implementation and the Beanshell distribution, so you can write IHandlers and States in Javascript or Beanshell without any further requirements.

For other languages, like groovy, python or ruby, you'll need their respective implementations for the Java platform. The BSF site holds information about where to find these implementations.

7.12. The Pustefix EventBus

Pustefix provides a simple Spring/annotation-based event mechanism following the publish-subscribe principle.

The event system is enabled by adding two bean definitions to the Spring configuration. A BeanPostProcessor for automatic registration of event subscribers (based on method annotations) and an EventBus responsible for publishing events to their subscribers.

  <bean id="eventPostProcessor" class="org.pustefixframework.eventbus.EventSubscriberBeanPostProcessor"/>
  <bean id="eventBus" class="org.pustefixframework.eventbus.EventBus"/>

A Spring bean can subcribe to an event by adding the @Subscribe method annotation. The method signature requires a single parameter of the event's type (subtypes or interfaces are supported too).

import org.pustefixframework.eventbus.Subscribe;

public class MySubscriberBean {

    @Subscribe
    public void listen(MyEvent event) {
        //process event 
    }

}

An event can be published by calling the publish method at the EventBus, The passed event object can be of an arbitrary type. The EventBus will immediately publish the event to all registered listeners, i.e. to all listeners which are registered for the event type, a subtype or one of its implemented interfaces.

import org.pustefixframework.eventbus.EventBus;

public class MyPublisherBean {
    
    @Autowired
    EventBus eventBus;
    
    public void doSomething() {
        //do something
        eventBus.publish(new MyEvent());
    }
    
}

7.13. Request IDs for log correlation

Request IDs let you correlate log entries with a specific web request. Pustefix can generate a unique ID for each request which is available during request processing, i.e. it can be added to each log entry created by a request. The ID is set by a servlet filter as request attribute, Log4J MDC key and HTTP response header.

To enable request IDs you have to add the following configuration to your application's web.xml:

  <filter>
     <filter-name>RequestIdFilter</filter-name>
     <filter-class>org.pustefixframework.http.RequestIdFilter</filter-class>
     <!-- supported init parameters with default values -->
     <!--
     <init-param>
       <param-name>mdcKey</param-name>
       <param-value>requestId</param-value>
     </init-param>
     <init-param>
       <param-name>attributeName</param-name>
       <param-value>requestId</param-value>
     </init-param>
     <init-param>
       <param-name>headerName</param-name>
       <param-value>X-Request-Id</param-value>
     </init-param>
     -->
   </filter>
   <filter-mapping>
     <filter-name>RequestIdFilter</filter-name>
     <url-pattern>/*</url-pattern>
     <dispatcher>REQUEST</dispatcher>
   </filter-mapping>

You can configure under which name the ID is stored in the different contexts. Setting the init parameter name to an empty value will disable it.

7.14. The Pustefix Editor

This has to be re-written from scratch. The old wiki did not contain any useful information.

Chapter 8. Using Spring MVC

8.1. Pustefix states as Spring MVC controllers

Pustefix States are very similar to Spring MVC Controllers. They are called for processing request data and return the data model needed to display a page. They are no native Controllers, which get automatically picked up by Spring MVC, but Pustefix supports delegating processing to Spring, if a State contains according Spring RequestMapping annotations.

public class TestState extends DefaultIWrapperState {

    @RequestMapping("/test/{myId}")
    public void list(@PathVariable String myId, Model model) {
        model.addAttribute("test", myId);
    }

A matching RequestMapping-annotated method is called after IHandlers are processed, but before the ResultDocument is created. Data added to the model will be automatically serialized to XML (after the configured output resources/beans are serialized).

8.2. AnnotationMethodHandlerAdapter customization (deprecated)

Spring MVC's request mapping and parameter binding (before Spring 5) can be customized by configuring the responsible AnnotationMethodHandlerAdapter instance. Pustefix by default registers such an instance, which can be customized by adding a bean definition of type org.pustefixframework.web.mvc.AnnotationMethodHandlerAdapterConfig to the Spring configuration. See the following example which configures two argument resolvers:

<bean class="org.pustefixframework.web.mvc.AnnotationMethodHandlerAdapterConfig">
   <property name="customArgumentResolvers">
      <list>
         <bean class="org.springframework.data.web.PageableArgumentResolver"/>
         <bean class="org.pustefixframework.web.mvc.filter.FilterResolver"/>
      </list>
   </property>
</bean>

If you're still using Spring 4.3, but you already want to use Spring's RequestMappingHandlerAdapter, you can do so by adding a custom RequestMappingHandlerAdapterConfig (see Section 8.3, “RequestMappingHandlerAdapter customization”).

8.3. RequestMappingHandlerAdapter customization

Spring MVC's request mapping and parameter binding can be customized by configuring the responsible RequestMappingHandlerAdapter instance. Pustefix by default registers such an instance, which can be customized by adding a bean definition of type org.pustefixframework.web.mvc.RequestMappingHandlerAdapterConfig to the Spring configuration. See the following example which configures two argument resolvers:

<bean class="org.pustefixframework.web.mvc.RequestMappingHandlerAdapterConfig">
   <property name="customArgumentResolvers">
      <list>
         <bean class="org.springframework.data.web.PageableHandlerMethodArgumentResolver"/>
         <bean class="org.pustefixframework.web.mvc.filter.FilterResolver"/>
      </list>
   </property>
</bean>

8.4. Example: Pagination

The following example shows how you can do pagination using the Spring Data approach (see see reference documentation).

By adding an argument of type org.springframework.data.domain.Pageable to the method, paging parameters like page number and page size are automatically bound to that bean. Then you can pass this bean to your backend to return the matching data page implementing org.springframework.data.domain.Page. Add the result to the model, so that the data, including some paging meta information, is automatically serialized to XML.

public class TestState extends DefaultIWrapperState {

    @Autowired
    private ContextData contextData;

    @RequestMapping("/test")
    public void list(Model model, Pageable pageable, Filter filter) {

        if(pageable.getSort() == null) {
            //initially create default Pageable 
            pageable = new PageRequest(0, 10, new Sort(Sort.Direction.ASC, "id"));
        }
        model.addAttribute("data", contextData.getDataList(pageable));
        model.addAttribute("filter", filter);
    }
}

The following parameters are bound to the Pageable bean:

  • page.size - number of items per page
  • page.page - the page number, starting with 1
  • page.sort - the property to be sorted by
  • page.sort.dir - sort direction: asc or desc

Often you not just want to paginate and sort data, but you also want to filter it, e.g. based on single property values or more complex conditions. Therefor Pustefix introduced the org.pustefixframework.web.mvc.filter.Filter interface. Filter parameters are also automatically bound, multiple parameters having the same name are interpreted as disjunction, different parameters as conjunction.

  • filter.foo=x
  • filter.foo=y
  • filter.bar=123

The filter parameters above will result in a Filter data structure representing the logical expression ( foo=x OR foo=y ) AND bar=123, i.e. by matching objects with property foo set to x or y and property bar set to 123. The default implementation supports checking an object candidate using the method Filter.isSatisfiedBy(candidate).

Chapter 9. Module Support

Modules allow you to share functionality, configuration options and view elements between different Pustefix applications.

9.1. Resources within library JARs

You can put resources into JAR files and make them available via the classpath. Such Pustefix modules can be normal JAR files, but must contain a special deployment descriptor, which has to be named META-INF/pustefix-module.xml in order to be recognized by Pustefix at build- and runtime. This deployment descriptor is a XML file with the following format:

<module-descriptor 
  xmlns="http://www.pustefix-framework.org/2008/namespace/module-descriptor"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.pustefix-framework.org/2008/namespace/module-descriptor
                      http://www.pustefix-framework.org/2008/namespace/module-descriptor.xsd">

  <!--
    The module name is used to construct the path the resources will be extracted to.
    In this example the path would be modules/mytest/.
  -->  
  <module-name>mytest</module-name>

  <!--
    Controls if module resources are editable via the CMS (if the module is loaded using
    the live mechanism). By default module resources are readonly.
  -->
  <content-editable>true</content-editable>

  <!-- Here you can define which folders under PUSTEFIX-INF contain static webapp resources 
       and should be publicly available. The resources then can be accessed under the URL 
       path '/modules/[module-name]/[static-path]'. If you only want to grant access depending 
       on the application using the module, you can alternatively configure the static module 
       path in the project configuration.
  -->
  <static>
    <path>css</path>
    <path>img</path>
    <!-- set i18n attribute to "true" for multitenancy/-language support -->
    <path i18n="true">errorpages</path>
  </static>

  <!--
    Here you can define if your module should be part of the dynamic search chain.
    Using the priority attribute you can influence the search order (lower value 
    means higher priority, leaving out the attribute the default priority is 10)
  -->
  <default-search priority="5">
    <!-- you can restrict dynamic search participation depending on a specific market 
         or language by specifying according filter-attributes -->
    <!--
    <filter-attribute name="tenant" value="DE_market"/>
    <filter-attribute name="lang" value="de"/>
    -->
  </default-search>

  <!--
    Here you can configure which resources from which other module should be
    overridden by your module (see "Dynamic resource resolution").
  -->
  <override-modules>
    <!-- you can restrict overriding depending on a specific market or language 
         by specifying according filter-attributes -->
    <!--
    <filter-attribute name="tenant" value="US_market"/>
    <filter-attribute name="lang" value="en"/>
    -->
    <module name="modulename">
      <resource path="path/to/resource"/>
      ...
    </module>
    ...
  </override-modules>

</module-descriptor>

Resources placed in module jar files not only can be accessed via the classpath, but Pustefix also supports loading them using its resource abstraction layer. Therefor you have to place the resources in the PUSTEFIX-INF folder.

Referencing such module resources is done by specifying the originating module using the module attribute, which is supported by all according tags used on configuration or view level, e.g. <config-include/>, <pfx:include/> and <pfx:image/>. For details on how to reference module resources and how to override modules, see Section 7.5, “Dynamic resource inclusion”.

Statusmessages placed under PUSTEFIX-INF/dyntxt within a module jar are automatically merged to src/main/webapp/modules-override/MODULENAME/dyntxt (see also Section 6.5, “StatusCodes”). You optionally can disable merging in the according plugin configuration and use dynamic resource lookup instead.

Static resources which should be publicly available/delivered by the server (e.g. resources referenced by HTML pages, like images, CSS or Javascript) can be made accessible by adding according <path/> entries to the <static/> section of the module descriptor. This is just a kind of whitelist saying which directories under PUSTEFIX-INF should be publicly available.

9.2. Creating new modules using the Maven archetype

Pustefix provides a Maven archetype, which can be used to create new modules. It sets up a maven project with pre-configured POM, deployment descriptor and statusmessage support.

mvn archetype:create \
-DarchetypeGroupId=org.pustefixframework.maven.archetypes \
-DarchetypeArtifactId=pustefix-archetype-module \
-DarchetypeVersion=0.16.5 \
-DgroupId=mytld.myorg.mysection \
-DartifactId=mymodule \
-Dversion=1.0  
      

You have to supply your own values for the groupId, artifactId and version parameters. The chosen artifactId will be used as default module and target folder name, the groupId as Java package name. The archetypeVersion should be replaced by the Pustefix version you're currently using. Executing the above command creates the following directory structure and artifacts:

mymodule
mymodule/src
mymodule/src/main
mymodule/src/main/java
mymodule/src/main/java/mytld
mymodule/src/main/java/mytld/myorg
mymodule/src/main/java/mytld/myorg/mysection
mymodule/src/main/java/mytld/myorg/mysection/mymodule
mymodule/src/main/resources
mymodule/src/main/resources/META-INF
mymodule/src/main/resources/META-INF/pustefix-module.xml
mymodule/src/main/resources/PUSTEFIX-INF
mymodule/src/main/resources/PUSTEFIX-INF/dyntxt
mymodule/src/main/resources/PUSTEFIX-INF/dyntxt/statuscodeinfo.xml
mymodule/src/main/resources/PUSTEFIX-INF/dyntxt/statusmessages.xml
mymodule/pom.xml
      

Building with mvn clean package creates a deployable Pustfix module. At the moment the only addition to the Maven standard build is the generation of StatusCode constant classes.

Chapter 10. Testing

Since release 0.13 Pustefix applications are regular Spring applications. Thus the benefit from building IoC based applications also applies to Pustefix: you can either make isolated, container-independent unit tests of your POJOs, or you can make integration tests within the Spring container.

10.1. Unit testing

Ideally your business logic is container and webframework-independent and you can test your components without any dependency to the Pustefix framework - unlike the view logic, which naturally depends on Pustefix framework classes.

The most frequently used Pustefix framework classes/interfaces are IWrappers, IHandlers and ContextResources.

In former Pustefix versions view logic objects collaborated using the Context object to programmatically retrieve the required references. Thus the objects were not only coupled to the Context object but also had implicit references to collaborating view objects.

Since release 0.13 you're recommended to wire your objects using Dependency Injection. So you often don't have a dependency to the Context object any more and the references to other objects are explicit. ContextResources implemented this way can be tested like other POJOs, in isolation and without providing a Context object.

But sometimes your ContextResource has to access the Context object (to call one of the various other methods) or you want to test an IHandler which still needs a Context object (passed as argument to the framework callback methods). If you want to test such an object in isolation, you can use a mock object that implements the Context interface.

The class org.pustefixframework.test.MockContext provides a very simple implementation of the Context interface. It doesn't really mimic the complex behaviour of the real implementation, but provides methods to set nearly all kind of state which can be accessed using this interface. It's intended to be used within isolated tests. Testing complex behaviour and object collaborations (like pageflow processing) has to be done as integration test using the real Context implementation.

The following example shows how you can test an IHandler, which uses a ContextResource to store data and will return true for needsData calls as long as no data has been submitted:

public class AdultInfoHandlerTest {

    public void testHandler() throws Exception {
        
        MockContext context = new MockContext();
        ContextAdultInfo info = new ContextAdultInfo();
        AdultInfoHandler handler = new AdultInfoHandler();
        handler.setContextAdultInfo(info);
        
        Assert.assertTrue(handler.needsData(context));
        
        AdultInfo iwrapper = new AdultInfo();
        iwrapper.init("info");
        iwrapper.setStringValAdult("false");
        iwrapper.loadFromStringValues();
             
        handler.handleSubmittedData(context, iwrapper);
        
        Assert.assertFalse(handler.needsData(context));
    
    }
    
}

You programmatically create a MockContext and instances of the required IHandlers, IWrappers and ContextResources. Then you wire your objects using the according setters. The first assertion is made by checking the handler's needsData method passing the MockContext. Then the IWrapper is populated with data and passed as argument to the handleSumittedData method. After that an assertion is made to check if needsData is satisfied now.

The above example worked with an injected ContextResource. If you have to test classes with implicit ContextResource references, which are retrieved using the ContextResourceManager, you also have to mock the ContextResourceManager. Therefore Pustefix provides the org.pustefixframework.test.MockContextResourceManager class:

public class AdultInfoHandlerTest {

    public void testHandler() throws Exception {
        
        MockContext context = new MockContext();
        MockContextResourceManager resourceManager = new MockContextResourceManager();
        context.setContextResourceManager(resourceManager);

        ContextAdultInfo info = new ContextAdultInfo();
        resourceManager.addResource(info);
        
    }
    
}

You have to create a MockContextResourceManager instance and set it at the MockContext object. Then you can create and add your ContextResource instances.

The above example programmatically created an IWrapper instance and set String data, which was casted and checked calling loadFromStringValues. The following example shows how the population of an IWrapper itself can be tested:

public class TShirtWrapperTest {

    public void testIWrapper() throws Exception {
        
        TShirt tshirt = new TShirt();   
        tshirt.init("shirt");
        tshirt.setStringValSize("MX");
        tshirt.loadFromStringValues();
        Assert.assertTrue(tshirt.errorHappened());
        IWrapperParam[] params = tshirt.gimmeAllParamsWithErrors();
        for(IWrapperParam param:params) {
            if(param.getName().equals("Color")) {
                Assert.assertSame(param.getStatusCodeInfos()[0].getStatusCode(), CoreStatusCodes.MISSING_PARAM);
            } else if(param.getName().equals("Size")) {
                Assert.assertSame(param.getStatusCodeInfos()[0].getStatusCode(), CoreStatusCodes.PRECHECK_REGEXP_NO_MATCH);
            }
        }

        tshirt.init("shirt");
        tshirt.setStringValSize("XL");
        tshirt.setStringValColor("1");
        tshirt.loadFromStringValues();
        Assert.assertFalse(tshirt.errorHappened());
        Assert.assertEquals(1, tshirt.getColor());

    }

}

First we instantiate and populate the IWrapper. After calling loadFromStringValues an assertion is made to check if an error occurred. Then we iterate over all parameters and assert the expected statuscodes.

After that we're initializing the wrapper again, this time with valid values. Now we can check if the wrapper returns the correct values (after they have been checked and casted calling the loadFromStringValues method). If an error happened during casting or checking, the value won't be set and the according getter method will return null.

10.2. Integration testing

Pustefix applications are regular Spring applications, thus all the integration testing benefit provided by Spring is also available for Pustefix: e.g. the IoC container caching between test executions, DI of test fixtures, etc.

Pustefix supports Spring's TestContext framework by providing a custom ContextLoader implementation. The following example shows how you can use it to set up a (pre JUnit-4.4) test:

@ContextConfiguration(loader=PustefixWebApplicationContextLoader.class,
                      locations={"file:projects/sample1/conf/project.xml","file:projects/sample1/conf/spring.xml"})
public class MyTest extends AbstractJUnit38SpringContextTests {
}

You set up the Pustefix specific ApplicationContext by setting the loader attribute of the org.springframework.test.context.ContextConfiguration annotation to org.pustefixframework.test.PustefixWebApplicationContextLoader. Using the locations attribute you can set the location of the ApplicationContext's configuration files (normally project.xml and spring.xml).

In this example we use a JUnit version prior to 4.4. Therefor we have to derive an according Spring class. If you use JUnit 4.4 or newer you can alternatively set a Spring-specific Runner implementation using the JUnit @RunWith annotation.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=PustefixWebApplicationContextLoader.class,
                      locations={"file:projects/sample1/conf/project.xml","file:projects/sample1/conf/spring.xml"})
public class MyTest {
}

Setting up your test this way, you can use Spring's autowiring to inject the required beans into your test class. The following example test uses a singleton scoped bean, which is automatically injected by its name using the Autowired and Qualifier annotations:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=PustefixWebApplicationContextLoader.class,
                      locations={"file:projects/sample1/conf/project.xml","file:projects/sample1/conf/spring.xml"})
public class MyTest {

    @Autowired
    @Qualifier("global_testdata")
    private TestData testData;
    
    @Test
    public void testBean() {
        Assert.assertEquals(testData.getText(), "bar");
    }
    
}

You aren't forced to use Spring's TestContext framework, alternatively you can manually use the Pustefix ContextLoader to create a PustefixWebApplicationContext, but you loose the benefit of context caching and dependency injection:

public class MyTest extends TestCase {

    public void testBean() {
        
        File docroot = new File("projects");
        PustefixWebApplicationContextLoader loader = new PustefixWebApplicationContextLoader(docroot);
        String[] locations = {"pfixroot:/sample1/conf/spring.xml", "pfixroot:/sample1/conf/project.xml"};
        PustefixWebApplicationContext appContext = (PustefixWebApplicationContext) loader.loadContext(locations);
     
        TestData testData = (TestData)appContext.getBean("global_testdata");
        assertEquals(testData.getText(), "bar");
        
    }
    
}

Now follows a more advanced example, which shows how you can test HTTP requests and session scoped beans outside of the application server using mock objects. This example shows how to request a page and test the resulting HTML document:

@ContextConfiguration(loader=PustefixWebApplicationContextLoader.class,
                      locations={"file:projects/sample1/conf/project.xml","file:projects/sample1/conf/spring.xml"})
public class HomePageTest extends AbstractJUnit38SpringContextTests {
    
    @Autowired
    private ServletContext servletContext;
    
    @Autowired
    private PustefixContextXMLRequestHandler requestHandler;
    
    public void testPageRequest() throws Exception {
   
        MockHttpServletRequest req = new MockHttpServletRequest();
        req.setPathInfo("/home");
        req.setMethod("GET");
        MockHttpServletResponse res = new MockHttpServletResponse();
        MockHttpSession session = new MockHttpSession(servletContext);
        req.setSession(session);
        RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(req));
        session.setAttribute(SessionHelper.SESSION_ID_URL, SessionHelper.getURLSessionId(req));
        
        requestHandler.handleRequest(req, res);
        Assert.assertTrue(res.getContentAsString().contains("<title>Pustefix Sample</title>"));
        
    }

}

First we create a mock object for the HttpServletRequest and set the path to the requested page. Then we're creating an according HttpServletResponse mock object. Next we create an HttpSession mock object an set it at the request. At last we have to set the request at Spring's RequestContextHolder and a special session attribute.

Now we can call the handleRequest method at the injected PustefixContextXMLRequestHandler, passing the request and response objects as arguments. At last we're checking if the resulting HTML from the HttpServletResponse contains the expected content.

The final example shows you how to test a pageflow by checking the collaborating IHandlers and States, isAccessible and needsData checks, submitting to handlers and checking of the ResultDocument:

@ContextConfiguration(loader=PustefixWebApplicationContextLoader.class,
                      locations={"file:projects/sample1/conf/project.xml","file:projects/sample1/conf/spring.xml"})
public class OrderFlowTest extends AbstractJUnit38SpringContextTests {
    
    @Autowired
    private ServletContext servletContext;
    
    @Autowired
    private Context pustefixContext;
    
    @Autowired
    private OverviewState overviewState;
    
    public void testHandler() throws Exception {
        
        MockHttpServletRequest req = new MockHttpServletRequest();
        req.setPathInfo("/home");
        req.setMethod("GET");
        MockHttpSession session = new MockHttpSession(servletContext);
        req.setSession(session);
        RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(req));
        PfixServletRequest pfxReq = new PfixServletRequestImpl(req,new Properties());
        ((ContextImpl)pustefixContext).prepareForRequest();
        ((ContextImpl)pustefixContext).setPfixServletRequest(pfxReq);
        
        Assert.assertFalse(overviewState.isAccessible(pustefixContext, pfxReq));
        AdultInfoHandler handler = (AdultInfoHandler)applicationContext.getBean(AdultInfoHandler.class.getName()+"#home#info");
        Assert.assertTrue(handler.needsData(pustefixContext));
        
        AdultInfo adultInfo = new AdultInfo();
        adultInfo.init("info");
        adultInfo.setStringValAdult("false");
        adultInfo.loadFromStringValues();
        handler.handleSubmittedData(pustefixContext, adultInfo);
        
        TShirtHandler tshirtHandler = (TShirtHandler)applicationContext.getBean(TShirtHandler.class.getName()+"#order#shirt");
        TShirt tshirt = new TShirt();
        tshirt.init("shirt");
        tshirt.setStringValColor("3");
        tshirt.setStringValSize("XL");
        tshirt.setStringValFeature(new String[] {"0","1","2"});
        tshirt.loadFromStringValues();
        tshirtHandler.handleSubmittedData(pustefixContext, tshirt);
        Assert.assertTrue(overviewState.isAccessible(pustefixContext, pfxReq));
        
        ResultDocument resDoc = overviewState.getDocument(pustefixContext, pfxReq);
        Document doc = resDoc.getSPDocument().getDocument();
        Node expNode = XMLUtils.parse("<adultinfo adult=\"false\"/>").getDocumentElement();
        XmlAssert.assertEquals(expNode, doc.getElementsByTagName("adultinfo").item(0));
    }
}

You have to be aware that some beans, e.g. States, require that the Context has set a current pagerequest and that the Context is prepared (meaning that some ThreadLocal initialization is done). So you can't just reference an arbitrary bean in the midst of the processing lifecycle and expect it to work outside of this lifecycle. Testing States at the moment requires a cast to its implementation class and some initialization (as shown in this example). But this can be subject to change in future Pustefix versions.

Chapter 11. Tooling

11.1. Pustefix internals page

The Pustefix internals page provides some useful information for application developers (e.g. about configuration and status of the system, the JVM and Pustefix itself). You can additionally find some useful links triggering actions like scheduling a webapp reload or invalidating all sessions.

The page is only available at development time and can be accessed under the URL path /pfxinternals (the <pfx:editconsole/> contains an according link too). On the different tabs you can find the following information:

  • Framework: Pustefix version and related links
  • Environment: Pustefix environment and system properties

Figure 11.1. Pustefix internals - Framework information

Pustefix internals - Framework information

  • JVM: JVM information and statistics, e.g. memory usage and garbage collection
  • System: Basic system information, like memory usage and CPU load

Figure 11.2. Pustefix internals - JVM information

Pustefix internals - JVM information

  • Cache: XSL caching statistics
  • Modules: Loaded Pustefix modules
  • Targets: XSL target dependency visualization, list of available XSL templates

Figure 11.3. Pustefix internals - Target generator information

Pustefix internals - Target generator information

  • Search: Full text search of files within webapp, modules and classpath

Figure 11.4. Pustefix internals - Full text search

Pustefix internals - Full text search

  • Actions: List of available actions, e.g. webapp or target generator reload, session invalidation, etc.
  • Message: Message board showing results of last triggered actions

11.2. Special parameters for development

Pustefix supports some special parameters triggering special behaviour/functions useful during development:

Table 11.1. Special parameters for development

Name Value Description
__xmlonly 1|2|3 Adding this parameter to an URL the page isn't rendered, but the originating DOM tree model is displayed. Setting the value to 1 the last DOM tree plus some additional data about the application state is rendered as HTML. Setting the value to 2 the pure XML is returned (always creating a new DOM tree for the current page). Setting the value to 3 the pure XML of the last DOM tree is returned.
__staticdom - Adding this parameter to an URL the page is rendered using the default DOM tree as created by the default State implementation. Not using the designated State can be useful for debugging, e.g. when the page isn't reachable because of pageflow or exception issues.

11.3. Maven plugins

Pustefix provides various Maven plugins, e.g. for generating code (IWrapper beans, Status code constants), for generating all pages of the view (XSL stylesheets) and other optional plugins.

11.3.1. Pustefix XSL Generate Plugin

This plugin generates the XSL stylesheets for all pages. It's useful for finding XSL errors before releasing and rolling out on production systems, additionally pre-generating the pages reduces the time initially spent when rendering a page for the first time.

The plugin also makes a simple analysis of the generated XSL files, e.g. detecting abnormally big XSL files potentially having bad impact on memory and performance (a summary is output at the end of the plugin execution).

      <plugin>
        <groupId>org.pustefixframework.maven.plugins</groupId>
        <artifactId>pustefix-generate-plugin</artifactId>
        <version>${pustefix.version}</version>
        <executions>
          <execution>
            <goals>
              <goal>generate</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

The plugin usage can be customized using the following configuration options:

      <configuration>
        <docroot>${basedir}/src/main/webapp</docroot> <!-- The document root directory. Default: src/main/webapp -->
        <parallel>true</parallel> <!-- Generate page XSLs in parallel using one thread per CPU core. Default: false -->
        <cleanup>false</cleanup> <!-- Remove intermediate output files after page XSLs are created. Default: true -->
        <maxPageSize>10m</maxPageSize> <!-- The maximum size of a generated page XSL file. Default: not set -->
        <maxPageSizeIgnore>myfile.xsl afile.xsl</maxPageSizeIgnore> <!-- XSL files to ignore when checking the maximum size. -->
        <maxTotalPageSize>0.5g</maxTotalPageSize> <!-- Maximum total size of all generated page XSL files. Default: not set -->
      </configuration>

The plugin will abort the build, if there are exceptions during the XSL page generation or configured maximum values are exceeded.

11.3.2. Pustefix Pagelist Plugin

This plugin generates a complete list with the display names of all renderable pages (respecting alias names, page alternatives, languages and tenants).

By default the plugin will be bound to the generate-test-resources phase and will write the pagelist to target/generated-test-resources.

      <plugin>
        <groupId>org.pustefixframework.maven.plugins</groupId>
        <artifactId>pustefix-pagelist-plugin</artifactId>
        <version>${pustefix.version}</version>
        <executions>
          <execution>
            <goals>
              <goal>generate</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

The plugin usage can be customized using the following configuration options (here showing the default values):

      <configuration>
        <docroot>${basedir}/src/main/webapp</docroot>
        <mode>prod</mode>
        <outputDirectory>${project.build.directory}/generated-test-resources</outputDirectory>
      </configuration>

Chapter 12. Upgrading to a newer Pustefix version

12.1. Upgrading from Pustefix 0.21 to 0.22

Pustefix now requires at least version 4.3 of the Spring framework (and supports version 5.0). If you rely on an older version you will have to update your application accordingly.

No Pustefix specific changes will be required to make your application work with Spring 4.3. If you want to switch to Spring 5 and you are using Pustefix states as MVC controllers together with custom WebArgumentResolvers, you have to migrate them to HandlerMethodArgumentResolvers and change the configuration from Section 8.2, “AnnotationMethodHandlerAdapter customization (deprecated)” to Section 8.3, “RequestMappingHandlerAdapter customization”.

12.2. Upgrading from Pustefix 0.20 to 0.21

After changing internal logging from Log4j to SLF4J, Pustefix no longer has a Maven dependency on Log4j or another logging implementation, it only brings a dependency to the SLF4J API (org.slf4j:slf4j-api), i.e. depending on the logging framework of your choice, you have to adapt your Maven dependencies, logging configuration and code:

  • If you want to stick with the old Log4j version, you have to add the Log4j library dependency (log4j:log4j), along with the dependency of the SLF4J-to-Log4j bridge (org.slf4j:slf4j-log4j12).

    No configuration or code change is required (pfixlog.xml is still supported).

  • If you want to use Logback, you have to add the according Logback library dependencies (at least ch.qos.logback:logback-classic) or you can alternatively add the Pustefix Logback extension module (org.pustefixframework.logging:pustefix-logback), which already has the required dependencies and provides some useful configuration extensions.

    You will have to migrate your old Log4j configuration (from pfixlog.xml) to a new Logback configuration (e.g. located at src/main/resources/logback.xml). Details about the migration can be found in the Logback documentation (see https://logback.qos.ch/manual/migrationFromLog4j.html). A base configuration for the Pustefix framework stuff comes as part of the Pustefix Logback module and can be included or used as reference, see Section 4.5.1, “Logback”).

    Code using the old Log4j API should be migrated to the SLF4J API. If you can't migrate all your legacy code, you can temporarily use a legacy API bridge (see https://www.slf4j.org/legacy.html).

  • If you want to use Log4j 2, you have to add the according Log4j 2 library dependencies (at least org.apache.logging.log4j:log4j-slf4j-impl, org.apache.logging.log4j:log4j-web and org.apache.logging.log4j:log4j-jcl) or you can alternatively add the Pustefix Log4j extension module (org.pustefixframework.logging:pustefix-log4j), which already has the required dependencies and provides some useful configuration extensions.

    You will have to migrate your old Log4j configuration (from pfixlog.xml) to a new Log4j 2 configuration (e.g. located at src/main/webapp/WEB-INF/log4j2.xml). Details about the migration can be found in the Log4j documentation (see https://logging.apache.org/log4j/2.x/manual/migration.html). A base configuration for the Pustefix framework stuff comes as part of the Pustefix Log4j module and can be included or used as reference, see Section 4.5.2, “Log4j 2”).

    Code using the old Log4j API either has to be migrated to the new Log4j 2 API or to the SLF4J API. If you can't migrate all your legacy code, you can temporarily use a legacy API bridge (see https://logging.apache.org/log4j/2.x/manual/migration.html and https://www.slf4j.org/legacy.html).

12.3. Upgrading from Pustefix 0.18 to 0.19

12.3.1. Library dependencies

Pustefix 0.19 itself is completely backwards compatible, i.e. neither API, behaviour, configuration or XSL templates have changed. But some of its dependencies on other Java libraries have changed.

The main change is the upgrade to Spring 4, i.e. if your application is relying on the transitive Maven dependencies of Pustefix, it will get Spring 4 after the upgrade too. If your application requires an older version of Spring, it has to explicitely specify an alternative version in its Maven POM now.

Another dependency change, that possibly affects your application, is the removal of the Apache ORO library. The library was used for regular expression checks in IWrappers and was replaced by using the JDK standard library. While Apache ORO regular expressions only slightly differ from the JDK ones, and Pustefix automatically tries to convert them on the fly, there's still a small chance that you're using some special expression which isn't compatible or can't be converted correctly.

To support you in finding these incompatible regular expressions, Pustefix, since version 0.18.64, checks regular expressions in IWrappers for compatibility and logs if they can't be converted or behave different. Just search your application's pustefix-servlet.log for occurrences of INCOMPATIBLE_REGEX (different result) or REGEX_ERROR (conversion error) to fix them manually (you should ensure first, that Log4J is configured accordingly, i.e. that warnings from logger de.schlund.pfixcore.generator.prechecks.RegexpCheck are really logged).

12.4. Upgrading from Pustefix 0.17 to 0.18

12.4.1. depend.xml

With Pustefix 0.18 the navigation tree in depend.xml became obsolete. You should completely remove the <navigation> element and its content. If you still need a declarative page structure available during the XSL transformations, you can alternatively move the child elements of <navigation> into an own file called sitemap.xml under the <sitemap> root tag (see Section 4.3.4, “Sitemap configuration”).

Making the navigation tree obsolete, the XSL parameter $navitree was also removed. If you're application's providing a sitemap.xml file, you can reference its content instead by using the new XSL parameter $sitemap. Without a sitemap.xml the parameter value will be empty.

If you didn't use the navigation tree within you XSL transformations you can remove the navigation without substitution because Pustefix itself doesn't need it any longer.

12.5. Upgrading from Pustefix 0.16 to 0.17

Pustefix 0.17 should be fully backwards compatible with 0.16. But the new render include mechanism required massive refactoring of the core XSL templates to make them independent of the page parameter on the master and metatags transformation levels. So you should invest some time into QA and especially check if buttons and links work as expected.

12.6. Upgrading from Pustefix 0.15 to 0.16

12.6.1. web.xml

With Pustefix 0.16 URLs referencing pages no longer contain the /xml/xyz path prefix. Thus the according servlet mappings in src/main/webapp/WEB-INF/web.xml became obsolete. You can remove all <servlet-mapping/> elements containing an <url-pattern> starting with /xml.

12.6.2. project.xml

In Pustefix 0.16 URL paths no longer start with /xml/xyz. You should adapt the according path configurations in src/main/webapp/WEB-INF/project.xml. Within the <application/> section you can remove the according path configurations: the <default-path> element and the <path/> elements beneath <context-xml-service/>, <direct-output-service/> and <deref-service/> (if available).

The <additional-trail-info> element now is optional. If missing, Pustefix by default registers the default AdditionalTrailInfo implementation org.pustefixframework.http.DefaultAdditionalTrailInfoImpl. You have to be aware that the package name of the interface and the implementation changed from de.schlund.pfixxml.perflogging to org.pustefixframework.http. You either will have to adapt the package names, or if you're relying on the default implementation, you can just remove the complete <additional-trail-info/> element.

The elements <perflogging> and <testrecording> became obsolete because the performance logging and testrecording mechanisms are no longer part of the Pustefix core framework. You can just remove these elements. The <authadmin> element is also no longer supported and should be removed.

If you're referencing a XML schema, you can change the schema location to http://www.pustefix-framework.org/2008/namespace/project-config-0_16.xsd to get an updated version.

12.6.3. depend.xml

With Pustefix 0.16 URLs of all kind of pages (dynamic, directoutput) are directly served under the servlet context without using an additional path prefix. Therefore every page requires a unique name. If you use the same page name multiple times, e.g. for normal and directoutput pages, you will have to rename it making it unique.

No longer requiring a path prefix for pages, you can remove all handler attributes from the <page/> elements within the <navigation/> section.

If you're referencing a XML schema, you can change the schema location to http://www.pustefix-framework.org/2008/namespace/xml-generator-config-0_16.xsd to get an updated version.

12.6.4. Tag library

The <pfx:image/> tag behaviour changed when used within XML includes coming from modules. Without specifying a module using the module attribute, the image source now is searched within the module itself instead of the webapp. You either have to add the search="dynamic" attribute to look up the image in the webapp before trying to get it from the module or you can set the module attribute to the special value WEBAPP to force getting it from the webapp.

12.6.5. Modules

Pustefix 0.16 no longer extracts resources from modules (the former resource-mappings from the Pustefix module descriptor are ignored now). Pustefix itself meanwhile can load every type of required resource directly from the module Jar files. If your application code directly accesses extracted resources, e.g. using java.io.File, you will have to change this and switch over to loading the resources via the classpath, ServletContext or Pustefix's resource abstraction layer.

12.6.6. Session handling

Since 0.16 Pustefix supports different session tracking strategies, whereas the new cookie based approach became the default strategy. Although Pustefix tries to make session id handling transparent to the application by hiding it in its core templates and internal code, there are use cases where applications directly access the session id, e.g. for creating links without using the core tags.

Because URL rewriting of the session id now by default is only done if the client disabled cookies, you will have to adapt code, which manually creates session-aware links. You should only include the session id, if necessary. Therefore Pustefix introduced two new XSL parameters: __sessionId, which is always set to the current session id, and __sessionIdPath, which is set to the path extension for the session on demand , e.g. ;jsessionid=xyz. The __sessionIdPath parameter will be empty if the session id came from a cookie. Thus you can easily create session-aware URLs by just adding the value to your URL without having to take care if cookies are enabled or not.

The XSL parameter __sessid was removed as its contract doesn't exactly match one of the new parameters. You will have to replace it according to your requirements using one the new ones.

12.6.7. AJAX webservices

The JSON and SOAP Javascript libraries were moved from the Pustefix core module to the Pustefix webservice modules. If your application uses these libraries, you will have to adapt the according script tags, replacing pustefix-core by pustefix-webservices-jaxws or pustefix-webservices-jsonws, e.g.

<script type="text/javascript" src="{$__contextpath}/modules/pustefix-core/script/webservice.js"></script>
<script type="text/javascript" src="{$__contextpath}/modules/pustefix-core/script/webservice_json.js"></script>

has to be changed to:

<script type="text/javascript" src="{$__contextpath}/modules/pustefix-webservices-jaxws/script/webservice.js"></script>
<script type="text/javascript" src="{$__contextpath}/modules/pustefix-webservices-jsonws/script/webservice_json.js"></script>

Generated Javascript SOAP stubs aren't delivered statically any longer, i.e. you also have to use the pfx:wsscript tag to include the stub file (instead of statically referencing it), e.g.

<script type="text/javascript" src="{$__contextpath}/xml/wsscript/MyService.js"></script>

has to be changed to:

<pfx:wsscript name="MyService" type="soap"/>

The type attribute only is required if you enabled multiple webservice protocols for the service within the configuration.

If you manually build webservice URLs, e.g. if you don't want to use the generated stubs, you have to be aware that the request path no longer contains the xml part, e.g. you will have to change http://anyhost/xml/webservice/MyService to http://anyhost/webservice/MyService.

Chapter 13. What's new (coming from the preceding release line)

Table of Contents

13.1. What's new in Pustefix 0.15.6
13.1.1. Module enhancements
13.2. What's new in Pustefix 0.15.7
13.2.1. Module enhancements
13.3. What's new in Pustefix 0.15.11
13.3.1. Configuration system enhancements
13.4. What's new in Pustefix 0.15.13
13.4.1. Module/configuration enhancements
13.4.2. Tooling enhancements
13.5. What's new in Pustefix 0.15.14
13.5.1. IWrapper enhancements
13.6. What's new in Pustefix 0.15.17
13.6.1. Module enhancements
13.7. What's new in Pustefix 0.16.0
13.8. What's new in Pustefix 0.16.5
13.9. What's new in Pustefix 0.17.0
13.10. What's new in Pustefix 0.18.0
13.10.1. Multi-tenancy
13.10.2. Multi-language
13.10.3. Sitemap and i18n pagenames
13.10.4. Page alternatives
13.10.5. Pustefix modules
13.10.6. Automatic configuration
13.11. What's new in Pustefix 0.18.2
13.11.1. Context information for XSLT errors
13.11.2. Switchable XSLT tooling extensions
13.11.3. Search-engine sitemap generation
13.11.4. Direct page alternative linking
13.11.5. OXM support for BigDecimal and BigInteger
13.12. What's new in Pustefix 0.18.5
13.12.1. CMIS support
13.12.2. Page aliases
13.13. What's new in Pustefix 0.18.6
13.13.1. Include part existence check
13.14. What's new in Pustefix 0.18.7
13.14.1. Custom sitemap attributes
13.15. What's new in Pustefix 0.18.9
13.15.1. Log directory configuration
13.16. What's new in Pustefix 0.18.13
13.16.1. Getting environment properties from within XSLT
13.17. What's new in Pustefix 0.18.14
13.17.1. External session invalidation synchronization support
13.18. What's new in Pustefix 0.18.27
13.18.1. Global output resources
13.18.2. Cookie-only session tracking
13.19. What's new in Pustefix 0.18.29
13.19.1. Contextual render includes
13.19.2. XSL parameters within include parameter XPath expressions
13.20. What's new in Pustefix 0.18.30
13.20.1. Object-to-XML mapping with JAXB
13.21. What's new in Pustefix 0.18.31
13.21.1. TargetGenerator tooling extensions toggle
13.22. What's new in Pustefix 0.18.34
13.22.1. Extended include parameter support
13.23. What's new in Pustefix 0.18.35
13.23.1. Early logging configuration
13.24. What's new in Pustefix 0.18.38
13.24.1. Full text search
13.24.2. Default page alternatives
13.25. What's new in Pustefix 0.18.39
13.25.1. Setting pfx:button page at runtime
13.26. What's new in Pustefix 0.18.42
13.26.1. Multitenancy/-language support for static resources
13.27. What's new in Pustefix 0.18.59
13.27.1. Rendering pages with static DOM tree during development
13.27.2. Added XSL extension elements for logging and debugging
13.28. What's new in Pustefix 0.18.63
13.28.1. Instance level XSLT extension functions
13.28.2. Preserving parameters on redirect
13.28.3. Reflective include information
13.29. What's new in Pustefix 0.18.64
13.29.1. Support standard Java regular expressions in IWrapper checks
13.29.2. Support multitenant property configuration
13.29.3. Configurable result DOM viewing
13.30. What's new in Pustefix 0.18.70
13.30.1. Page alias names with slashes
13.31. What's new in Pustefix 0.18.71
13.31.1. Environment-dependent Spring property files
13.32. What's new in Pustefix 0.18.87
13.32.1. New XPath string functions using pattern matching
13.33. What's new in Pustefix 0.19.0
13.34. What's new in Pustefix 0.19.4
13.35. What's new in Pustefix 0.19.8
13.36. What's new in Pustefix 0.19.9
13.37. What's new in Pustefix 0.19.11
13.38. What's new in Pustefix 0.19.14
13.39. What's new in Pustefix 0.19.15
13.40. What's new in Pustefix 0.19.17
13.41. What's new in Pustefix 0.19.21
13.42. What's new in Pustefix 0.19.23
13.43. What's new in Pustefix 0.19.24
13.44. What's new in Pustefix 0.19.25
13.45. What's new in Pustefix 0.19.27
13.46. What's new in Pustefix 0.19.28
13.47. What's new in Pustefix 0.19.29
13.48. What's new in Pustefix 0.19.30
13.49. What's new in Pustefix 0.20.0
13.50. What's new in Pustefix 0.20.1
13.51. What's new in Pustefix 0.20.14
13.52. What's new in Pustefix 0.20.19
13.53. What's new in Pustefix 0.20.20
13.54. What's new in Pustefix 0.20.25
13.55. What's new in Pustefix 0.20.26
13.56. What's new in Pustefix 0.20.27
13.57. What's new in Pustefix 0.20.28
13.58. What's new in Pustefix 0.20.29
13.59. What's new in Pustefix 0.20.31
13.60. What's new in Pustefix 0.21.0

13.1. What's new in Pustefix 0.15.6

13.1.1. Module enhancements

Introduced pre-defined special module names WEBAPP and PAGEDEF, which can be set as module values at <pfx:include> and <pfx:image> tags to explicitly refer to resources from the webapp folder or resources from within the module where a page is defined (see Special module names).

The config-include mechanism now supports directly referencing config fragment files from modules by adding a module attribute to the according <config-include> elements (see Section 4.3.3, “Page Configuration (depend.xml)” and Section 4.3.5, “ContextXMLService configuration file”).

13.2. What's new in Pustefix 0.15.7

13.2.1. Module enhancements

Static web resources now can be delivered directly from modules without having to extract them to the webapp folder first by the build process. Thus it's no longer necessary to define resource mappings in the module's descriptor file (see Chapter 9, Module Support). Making resources from modules publicly available now just requires to define an according static path in the project configuration, e.g. <path>/modules/mymodule/mypath/img</path> makes available the according folder /PUSTEFIX-INF/mypath/img from the module/JAR file mymodule.

Status message files no longer require to be extracted using a resource mapping. Now they're automatically detected within module jars and merged to the according modules-override folder.

13.3. What's new in Pustefix 0.15.11

13.3.1. Configuration system enhancements

We improved the configuration customization support by making context init parameters available as environment properties and thus usable as XPath variables during the customization process (see Section 4.2, “Customization tools”).

Besides we dropped the storage of environment properties during the build (and the reusage at runtime). Thus a WAR file doesn't contain build-dependent settings any longer and can be used in different environments. Environment specific config customizations can be controlled by setting according context init parameters.

13.4. What's new in Pustefix 0.15.13

13.4.1. Module/configuration enhancements

Including configuration fragments from modules now can be done without having to directly specify the module names. Therefor the config-include element now supports module attribute values containing simple patterns, which will be matched against the names of the modules available in the classpath, e.g. module="*" will include all according configuration fragments found in the classpath, module="*-us" will include only fragments from modules whose name ends with -us.

Added support for conditional unpacking of module resources. Within the module descriptor you can set the optional resources element's attribute unpack to obsolete. Thus the resources won't be extracted in the current or newer Pustefix versions (despite containing resource mappings). If you still want that obsolete resources are extracted, you can configure the Maven pustefix-webapp-plugin by adding <unpackObsolete>true</unpackObsolete> to its configuration. This should help migrating modules and applications towards avoiding extracting resources at all, while keeping backwards compatibility for modules shared between different applications (see Chapter 9, Module Support).

13.4.2. Tooling enhancements

When hovering the closing include part delimiters displayed in edit mode, the tooltip now shows additional information for dynamic includes. It prints out the complete dynamic search chain, e.g. something like webapp(?) module-a(-) module-aa(+) module-ab(+), whereas (?) means that the file doesn't exist, (-) says that the file exists but doesn't contain the part, and (+) implies that a file containing the part is found.

Introduced the Pustefix internals page. Similar to the DOM tree view this page provides some additional information useful during development, e.g. environment, JVM and module information (see Section 11.1, “Pustefix internals page”).

13.5. What's new in Pustefix 0.15.14

13.5.1. IWrapper enhancements

Annotation-based IWrappers now also support assigning IHandler beans by name. Therefor the @IWrapper annotation provides the new beanRef attribute (see Section 7.9.1, “IWrapper annotations”).

13.6. What's new in Pustefix 0.15.17

13.6.1. Module enhancements

Modules now can define which directories contain static resources and should be publicly available. Thus you don't have to configure it in every application using the module. This configuration is done in the module deployment descriptor using <path/> elements within the <static/> top-level section (see Chapter 9, Module Support).

13.7. What's new in Pustefix 0.16.0

Pustefix 0.16.0 focuses on SEO. It brings more user and search engine friendly URLs. The main features are:

  • New session tracking strategies:

    • Cookie-based session tracking
    • Specialized session tracking strategy for search engine bots

  • URL cleanup

    • Page URLs without /xml prefix
    • Use __frame parameter only on demand
    • Sessionless URLs
    • Home page under / (without redirect)

If you're migrating an existing appliation to Pustefix 0.16.0, you should have a look at Chapter 12, Upgrading to a newer Pustefix version.

13.8. What's new in Pustefix 0.16.5

Pustefix 0.16.5 brings some CMS improvements. The CMS editor now supports editing of resources coming from modules (if they're loaded via the live mechanism). Modules are readonly by default, but can be made editable by adding <content-editable>true</content-editable> to the module descriptor (see Section 9.1, “Resources within library JARs”).

13.9. What's new in Pustefix 0.17.0

Pustefix 0.17.0 focuses on XSLT rendering improvements. It brings the so-called render extensions, a mechanism which allows to call an extension function to produce additional output and write it to the result of the main transformation (on the last transformation level).

The default render extension implementation, the so-called render include mechanism, supports the standalone rendering/outputting of include parts. Thus you're able to include parts at runtime, e.g. based on the data from the result tree. In contrast to the traditional runtime includes, which are restricted to simple content and a subset of tags available on the last transformation level, the render includes support arbitrary content because the metatags and master XSLT transformations are also applied (see Section 5.3.4, “Dynamically including parts at rendering time”).

Render include parts not just can be rendered within a regular main transformation, but can be also rendered standalone, i.e. you can request a rendered part via AJAX to dynamically update your page.

If you're migrating an existing appliation to Pustefix 0.17.0, you should have a look at Chapter 12, Upgrading to a newer Pustefix version.

13.10. What's new in Pustefix 0.18.0

Pustefix 0.18.0 focuses on new internationalization features. It brings multi-tenancy on the view layer, supporting different views for different tenants (like countries or markets) within a single application instance, along with runtime-switchable multi-language support, including internationalized page names.

13.10.1. Multi-tenancy

Pustefix 0.18 provides multi-tenancy support on the view layer, i.e. it can create and deliver alternative versions of a page depending on a selected tenant. Tenants are automatically selected by matching host name patterns against the requested host name. Alternative page versions are created by applying filters to the dynamic include mechanism, thus overriding and extending the core page content with tenant- and/or language-specific content from according tenant/language modules (see Section 7.4, “Multitenancy”).

13.10.2. Multi-language

Up to Pustefix 0.17 the only way to make the language of your application switchable at runtime was using the <pfx:langselect> tags to directly embed all language dependent content into the same part and theme side by side. With Pustefix 0.18 it's possible to automatically create different versions of a page serving different languages using the dynamic override mechanism, e.g. you can source out all language dependent content into own modules (language packs) which override a language independent core module (see Section 7.3, “Internationalization”).

13.10.3. Sitemap and i18n pagenames

Pustefix 0.18 differentiates between logical page names and display page names. Thus you can rename pages or provide internationalized page names without having to change existing page references (to the logical page name). Such page aliases are configured in a file called sitemap.xml which is replacing the navigation configuration from depend.xml (see Section 4.3.4, “Sitemap configuration”).

13.10.4. Page alternatives

Page alternatives are a new concept for creating different representations of a page (including possible variants). Page alternatives are internally used to create different versions for languages/tenants, but can be also used for pages which are based on a common page, assigned to the same logical page, but with different content and display pagename, e.g. landing pages (see Section 7.2, “Page alternatives”).

13.10.5. Pustefix modules

The module mechanism has been extended to support sourcing out every kind of resource into modules, e.g. you can put arbitrary configuration files into modules (like project.xml, depend.xml or spring.xml). The Pustefix resource loading mechanism has been extended and now additionally implements Spring's Resource abstraction layer. Thus you can use module URIs within the Spring configuration, e.g. for injecting module resources into beans.

The dynamic search mechanism now supports adding modules to the default search chain. This is declaratively done in the module descriptor pustefix-module.xml. You can additionally set a search priority to define a search order. Using filter attributes you can define a dynamic search filter allowing to include/exclude certain modules from searching/overriding, which is useful for multi-tenant/language applications to filter by tenant or language (see Section 9.1, “Resources within library JARs”).

13.10.6. Automatic configuration

Pustefix 0.18 extends the depend.xml configuration by providing a <auto-standardpage> element which can be used as template, which automatically creates standardpage definitions for pages found in a specified folder (see Page auto-configuration).

13.11. What's new in Pustefix 0.18.2

13.11.1. Context information for XSLT errors

The default error page for XSLT errors now provides additional contextual information. The error line from the XML file causing the error is cut out (including some surrounding lines) and added to the error page.

13.11.2. Switchable XSLT tooling extensions

In prior versions the XSLT tooling extensions (dynamic include information, console, etc.) were always enabled if not running in production mode. Now you can selectively enable/disable the tooling extensions in all modes (except production mode). Therefore Pustefix introduced the configuration element <tooling-extensions> (see Section 4.3.2, “Project descriptor (project.xml)”).

Besides the XSLT tooling has been decoupled from the editor settings, i.e. you can use the include information tooling without having the editor enabled.

13.11.3. Search-engine sitemap generation

Pustefix now supports generating search-engine sitemaps according to the sitemaps protocol (see Sitemaps protocol specification). Sitemap generation can be enabled by adding the <searchengine-sitemap/> element to the project configuration (see Section 4.3.2, “Project descriptor (project.xml)”).

13.11.4. Direct page alternative linking

In prior Pustefix versions links to page alternatives created with <pfx:button> referenced the logical page and the application logic was responsible for detecting/setting the page alternative and thus triggering a redirect to the according page alternative by Pustefix.

Now <pfx:button> supports directly setting the page alternative key using the altkey attribute or <pfx:altkey> element. Thus Pustefix can directly reference the according page alternative name without having to do a redirect (see Section 5.2.1, “pfx:button”).

13.11.5. OXM support for BigDecimal and BigInteger

Pustefix's object-to-XML mapping mechanism now can serialize BigDecimal and BigInteger objects.

13.12. What's new in Pustefix 0.18.5

13.12.1. CMIS support

Added basic support for loading resources from content management systems using CMIS/AtomPub.

13.12.2. Page aliases

Added XPath function to get current display page name during rendering (see the section called “Page functions”).

13.13. What's new in Pustefix 0.18.6

13.13.1. Include part existence check

Added support for checking the existence of an include part during rendering, e.g. for displaying an include part depending on the existence of another include part (see Section 5.3.5, “Checking include part existence (<pfx:checkinclude>)”).

13.14. What's new in Pustefix 0.18.7

13.14.1. Custom sitemap attributes

Added support for using custom XML attributes at page elements within the sitemap configuration (see Section 4.3.4, “Sitemap configuration”).

13.15. What's new in Pustefix 0.18.9

13.15.1. Log directory configuration

Added support for setting the log directory using the context init parameter logroot. The directory can be referenced in the log configuration file pfixlog.xml using the custom tag <cus:logroot/>.

13.16. What's new in Pustefix 0.18.13

13.16.1. Getting environment properties from within XSLT

Added support for getting environment from within XSLT using the XPath function pfx:getEnvProperty (see Section 5.7.1, “XPath functions”).

13.17. What's new in Pustefix 0.18.14

13.17.1. External session invalidation synchronization support

Invalidating sessions outside of Pustefix, e.g. within a ServletFilter or Spring WebRequestInterceptor, can break concurrently running requests operating on the same session. Pustefix now can block such a session invalidation until all requests using this session are processed. Therefor Pustefix provides the new utility method SessionUtils.invalidate(session), which can be used instead of directly calling invalidate() at the HttpSession object.

13.18. What's new in Pustefix 0.18.27

13.18.1. Global output resources

Added support for global output resources, i.e. output resources/ContextResources can be globally declared, thus being automatically added as output resources to all pagerequests. That's an alternative solution for adding global data and could replace the necessity of doing this programmatically via a basic State implementation. Global resources can be added to the new <global-output/> section of the context configuration (see Section 4.3.5, “ContextXMLService configuration file”).

13.18.2. Cookie-only session tracking

Added new sessiontracking strategy which forces cookie-based session tracking without falling back to URL rewriting when the browser has cookies disabled. The new strategy can be enabled by setting <session-tracking-strategy> to COOKIEONLY in the project configuration (see Section 4.3.2, “Project descriptor (project.xml)”).

13.19. What's new in Pustefix 0.18.29

13.19.1. Contextual render includes

Added support for setting the context node of the main transformation as the context node of a render include sub-transformation by specifying render parts as contextual. (see Section 5.3.4, “Dynamically including parts at rendering time”).

13.19.2. XSL parameters within include parameter XPath expressions

Added support for using XSL parameters within include parameter XPath expressions, e.g. you can access the page and sitemap parameters to dynamically create include part names.

13.20. What's new in Pustefix 0.18.30

13.20.1. Object-to-XML mapping with JAXB

Pustefix's object-to-XML mapping layer now alternatively supports marshalling via JAXB, e.g. ContextResources annotated with the according JAXB annotation now are automatically serialized to the DOM tree via JAXB instead of the builtin serialization mechanism (see Section 7.8.4, “Using JAXB serialization”).

13.21. What's new in Pustefix 0.18.31

13.21.1. TargetGenerator tooling extensions toggle

Pustefix now supports toggling of the XSL tooling extensions at runtime. Thus you can generate pages in development mode being fully identical with the production version, which can be very helpful if you're debugging problems caused by the additional markup from the XSL tooling (like whitespace differences). The toggle can be found as action on the Pustefix internals page (see Section 11.1, “Pustefix internals page”).

13.22. What's new in Pustefix 0.18.34

13.22.1. Extended include parameter support

More Pustefix templates now support using include parameter expressions in attribute values (see Section 5.3.6, “Include parameters (<pfx:includeparam>)”).

13.23. What's new in Pustefix 0.18.35

13.23.1. Early logging configuration

Pustefix now supports setting up Log4j at early application startup time using a ServletContextListener, thus being able to log errors/messages occurring before the Spring application context is started (see Section 4.5, “Logging configuration ”).

13.24. What's new in Pustefix 0.18.38

13.24.1. Full text search

Pustefix's development tooling now offers a full text search (at runtime). The search can be found on the pfxinternals page and supports searching the webapp, modules and the classpath using file name patterns and regular expressions (see Figure 11.4, “Pustefix internals - Full text search”).

13.24.2. Default page alternatives

Now you can choose a page alternative to be the default page alternative for a page. Thus linking or directly calling the logical page will trigger the according default page alternative (see Section 4.3.4, “Sitemap configuration”).

13.25. What's new in Pustefix 0.18.39

13.25.1. Setting pfx:button page at runtime

The <pfx:button/> template now supports setting the page name at runtime using the <pfx:page/> child element (see Section 5.2.1, “pfx:button”).

13.26. What's new in Pustefix 0.18.42

13.26.1. Multitenancy/-language support for static resources

Static resources now can be delivered in language/tenant-specific versions, whereas tenant/language act as additional path components, e.g. /bar/foo.gif is searched under /bar/en_CA/foo.gif and /bar/en/foo.gif first (see example in Section 7.4, “Multitenancy”). This feature can be enabled on directory basis by addding the boolean attribute i18n to the according static path configurations (see Section 4.3.2, “Project descriptor (project.xml)” and Section 9.1, “Resources within library JARs”).

13.27. What's new in Pustefix 0.18.59

13.27.1. Rendering pages with static DOM tree during development

Introduced new special parameter __staticdom, which lets you render pages with the static DOM tree provided by the default State, e.g. for debugging view problems (see Section 11.2, “Special parameters for development”).

13.27.2. Added XSL extension elements for logging and debugging

Added some XSL extension elements extending the standard way of outputting messages in XSL by supporting log level and Log4J loggers. Added extension element for fail-safe XSLT processing, e.g. on tooling or test pages (see Section 5.7.2, “XSL extension elements”).

13.28. What's new in Pustefix 0.18.63

13.28.1. Instance level XSLT extension functions

Using the new XSLT extension function pfx:getBean() you can look up Spring beans and pass them as first argument to XSLT extension functions for making instance level method calls (see the section called “Miscellaneous functions”).

13.28.2. Preserving parameters on redirect

Sometimes request parameters should be preserved when redirecting after a request has been processed, e.g. for tracking purposes. See Section 4.3.5, “ContextXMLService configuration file” for details about the <preserve-params> configuration.

13.28.3. Reflective include information

Pustefix now provides some reflective include information during the XSL transformation. Using the new pfx:getIncludeInfo XPath function you can query the names of all parts contained in a file, e.g. useful to automatically include generated include parts based on naming conventions (see the section called “Include functions”).

13.29. What's new in Pustefix 0.18.64

13.29.1. Support standard Java regular expressions in IWrapper checks

IWrapper parameters now can be checked using standard Java regular expressions instead of the special Perl5/Apache ORO expressions. To be backwards-compatible existing expressions, starting with /, still will be handled as Perl5 expressions. Other expressions will be treated as Java standard expression. Future Pustefix versions will completely remove the Apache ORO and convert Perl5 expressions to Java standard expression under the hood.

13.29.2. Support multitenant property configuration

Properties within the Pustefix configuration files now can be configured tenant-dependent. The tenant can be specified using a tenant attribute, e.g. <prop name="foo" tenant="UK">bar</prop> (see Section 4.3.1, “XML property files syntax”).

13.29.3. Configurable result DOM viewing

Viewing of the result DOM now is configurable using the <show-dom/> configuration element (see Section 4.3.2, “Project descriptor (project.xml)”). Additionally you can mark selected sessions as debuggable via JMX and thus make the DOM tree available for debugging purposes on production systems too.

13.30. What's new in Pustefix 0.18.70

13.30.1. Page alias names with slashes

Pustefix now supports using slashes in page alias names, e.g. mysection/mypage, which can help to create more user- or SEO-friendly URLs.

13.31. What's new in Pustefix 0.18.71

13.31.1. Environment-dependent Spring property files

Pustefix provides a new PropertyPlaceholderConfigurer implementation which can be used to organize Spring properties in different environment-dependent files (see Section 4.4.2, “Environment-dependent Spring property files”).

13.32. What's new in Pustefix 0.18.87

13.32.1. New XPath string functions using pattern matching

Pustefix provides the new XPath functions pfx:matches() and pfx:replace(), which basically are a backport of the according XPath 2.0 function, making pattern matching available in XSLT 1 (see the section called “String functions”).

13.33. What's new in Pustefix 0.19.0

Pustefix 0.19.0 focuses on upgrading the underlying technologies/libraries. It added support for Servlet API 3 and Spring 4. The legacy dependency to Apache ORO was removed, i.e. stuff like regular expressions and LRU caches now are implemented completely based on the JDK standard library.

13.34. What's new in Pustefix 0.19.4

Beginning with this version the Pustefix 0.19 line becomes the main development line. All new features from 0.18 now are available in 0.19 too and the development of the 0.18 line will be discontinued. It's recommended to upgrade existing applications to 0.19 soon (see Section 12.3, “Upgrading from Pustefix 0.18 to 0.19”).

13.35. What's new in Pustefix 0.19.8

Passing through the last pageflow using the __lf URL parameter has become optional. It can be disabled using the new configuration switch <disable-pageflow-passthrough/> (see configuration section).

13.36. What's new in Pustefix 0.19.9

Pustefix now supports modularized log4j configurations and provides an appropriate default configuration, which helps keeping the logging configuration up-to-date with the latest Pustefix version and simplifies the maintenance of complex application logging configurations (see logging configuration). Additionally Pustefix now allows changing of log levels at runtime via JMX.

13.37. What's new in Pustefix 0.19.11

Extended form error checking tag by optionally supporting IWrapper parameter prefix/name and request trigger conditions, and adding a new tag with inverted semantics (see tag reference).

Improved render include Javascript library: support for passing arguments as JSON object, HTTP error callback functions (see Javascript example).

13.38. What's new in Pustefix 0.19.14

Added new Maven plugin for generating a complete list with display names of all renderable pages, e.g. can be used for automatic testing. (see Section 11.3.2, “Pustefix Pagelist Plugin”).

Beautified and more SEO-friendly URLs by removing the __reuse parameter from redirect URLs (replaced by Spring flash attributes).

Pustefix now provides a simple Spring/annotation-based event mechanism. It should replace the deprecated way of coupling ContextResources using the ContextResourceObserver and ObservableContextResource interfaces (see Section 7.12, “The Pustefix EventBus”).

13.39. What's new in Pustefix 0.19.15

Added support for rendering pages with non-200 status code (see SPDocument.setResponseStatus()) and overriding declared error pages (see SPDocument.setResponseErrorPageOverride()).

13.40. What's new in Pustefix 0.19.17

Added support for Spring profiles. Pustefix now by default activates a profile for the current execution environment mode (see Section 4.4.3, “Environment-dependent Spring profiles”).

13.41. What's new in Pustefix 0.19.21

Added support for using Google Closure Compiler to compress inline Javascript (default compressor will be the YUI compressor, if available in the classpath).

13.42. What's new in Pustefix 0.19.23

Improved rendering perforamnce (by extension function caching).

13.43. What's new in Pustefix 0.19.24

Added new DefaultIWrapperState method handleWrapperErrors for processing validation errors, e.g. to log them.

13.44. What's new in Pustefix 0.19.25

Added new Context method invalidateSessionAfterCompletion for guaranteed invalidation of session after current request has been processed.

13.45. What's new in Pustefix 0.19.27

Added new tool for analyzing XSL transformation performance.

13.46. What's new in Pustefix 0.19.28

Improved rendering performance for pages with lots of runtime includes (by include extension function caching).

Added servlet filter for performance logging.

13.47. What's new in Pustefix 0.19.29

Improved rendering performance for pages with lots of internal links (by caching callbacks).

13.48. What's new in Pustefix 0.19.30

We officially discontinue support for Java 1.7! This is the first version which makes use of Java 1.8 features. Almost a year after the EOL of Java 1.7 it's really about time to say goodbye.

Added support for generating request IDs which let you correlate log entries with specific web requests (see Section 7.13, “Request IDs for log correlation”).

Added Eclipse m2e lifecycle mappings for Pustefix plugins to improve Eclipse IDE integration of Pustefix projects.

13.49. What's new in Pustefix 0.20.0

Added support for custom page URL paths: you can group pages using a common path prefix (so-called page page groups, see Section 4.3.4, “Sitemap configuration”) or map pageflow names to a URL path prefix.

13.50. What's new in Pustefix 0.20.1

Added a DOM history in development mode, i.e. on the DOM view page, you can not only view last DOM but select an older version from a list of the previously used DOMs.

Added support for headless rendering of Pustefix pages, i.e. you can programmatically do the rendering of a page or part and process the resulting content on the server side.

13.51. What's new in Pustefix 0.20.14

Pustefix backported some useful XPath 2.0 functions to make them avaiable with XSLT 1: pfx:encode-for-uri(), pfx:string-length() and pfx:substring() (see the section called “String functions”).

Extended the pfx:button template to support the creation of REST-style links using the new @path attribute, or the pfx:path/pfx:segment elements (see Section 5.2.1, “pfx:button”).

Added the new method getModelAndView() to the ResultDocument class. This enables you to retrieve the ModelAndView instance created by Spring MVC when using RequestMappings, e.g. when overriding the getDocument() method of a State and you need to check for model changes.

13.52. What's new in Pustefix 0.20.19

Added <pfx:checkmessage> and <pfx:checknomessage> tags for checking if a MessageSource message is existing or not (see Section 5.4.1, “Message tags”).

Added support for configuring the Spring MessageSource in project.xml, which makes it available during page pre-generation too (see Section 5.4.2, “MessageSource configuration”).

13.53. What's new in Pustefix 0.20.20

Added support for markup in message arguments and strings (unescaped rendering is enable by setting the new <pfx:message> attribute disable-output-escaping to yes (see Section 5.4.1, “Message tags”).

13.54. What's new in Pustefix 0.20.25

Added support for configuring if a matching RequestMapping method is call before/after IHandlers are processed. By default the RequestMapping is called first. This behaviour can be changed by setting <input premvc="true"/> (see Section 4.3.5, “ContextXMLService configuration file”).

13.55. What's new in Pustefix 0.20.26

Added multilanguage support for projects without tenants, i.e. specifying multiple supported languages in project.xml, an own version of a page per language can be generated (like it's already done for multitenant projects with multiple languages).

Added support for the Spring Locale mechanism, i.e. by default Pustefix makes its currently set language available via Spring's LocaleResolver mechanism, e.g. for passing the Locale to request dispatching methods.

13.56. What's new in Pustefix 0.20.27

Added support for session tracking modes introduced with Servlet API 3.0, i.e. the Pustefix SessionTrackingStrategy now is derived from the session tracking mode setting done in web.xml (or directly via Servlet API). The <session-tracking-strategy> configuration element becomes deprecated and should be replaced by according <tracking-mode> settings in web.xml (by default COOKIE and URL tracking modes are set, which corresponds to the Pustefix COOKIE session tracking strategy using cookies with URL fallback, if you want COOKIEONLY strategy, you have to set the COOKIE tracking mode only).

13.57. What's new in Pustefix 0.20.28

Added support for setting the expiration time of static resources. You can set a default time for all resources or specify it per content type. The setting is done within the <static> element of the project configuration file (see Section 4.3.2, “Project descriptor (project.xml)”).

Pustefix now can generate CSP nonce randoms for explicitely permitting inline script, i.e. if you add an according response header, e.g. <prop name="responseheader.Content-Security-Policy">script-src 'self' 'nonce-[NONCE]';</prop>, Pustefix will replace [NONCE] by a secure random value, which will be automatically added as nonce attribute to the script elements of a page (if using the <pfx:script> tag). The value will be also accessible as nonce XSLT parameter.

13.58. What's new in Pustefix 0.20.29

Added support for generating lastmod elements into the searchengine sitemap (see Sitemaps protocol specification). Therefor you have to add according lastmod attributes to the page or alt elements within the Pustefix sitemap.xml configuration file (see Section 4.3.4, “Sitemap configuration”).

13.59. What's new in Pustefix 0.20.31

Pustefix states acting as Spring MVC controllers (Section 8.1, “Pustefix states as Spring MVC controllers”) now support redirecting using redirect prefixes, i.e. a request mapped method can return a view name prefixed with redirect: or you can set an according view name at the ModelAndView object. The redirect will be handled by Spring, thus URI template variables are working too.

13.60. What's new in Pustefix 0.21.0

Pustefix now uses SLF4J as logging framework. It still supports the, meanwhile deprecated, first version of Log4j, but you're recommended to switch over to Logback or Log4j 2 instead. For both frameworks Pustefix provides some useful extensions (see Section 4.5, “Logging configuration ” and Section 12.2, “Upgrading from Pustefix 0.20 to 0.21” for details).