This documentation relates to the latest Pustefix release from the 0.20 line.
Copyright © 2007-2016
Table of Contents
List of Figures
List of Tables
List of Examples
<pfx:checkfield>
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:
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.
Table of Contents
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.
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.
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.
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.
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”).
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”).
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”).
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.
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).
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.
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”).
Table of Contents
Before we can get started, you have to make sure that some requirements are met by your development environment. You will need:
The installation of these tools is not covered by this tutorial. Please refer to the documentation provided with these tools for installation instructions.
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.
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.
Table of Contents
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.
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.
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 application runs within a Spring ApplicationContext
that is created by the DispatcherServlet
. The servlet dispatches
all requests to HttpRequestHandler
s 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
).
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.
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.
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
.
Table of Contents
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
.
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.
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.
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.
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.
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.
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 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_+- |
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. |
<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"/>
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). |
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>
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>
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>
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>
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 | |
---|---|
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 |
</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 | |
---|---|
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:
If state is given, use the value of it's
class
attribute. When no scope attribute is present, singleton-scope
is used by default.
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:
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 | |
---|---|
If you're referencing a custom Spring bean using
That's why Pustefix supports the referencing of parent bean definitions using the |
<input premvc="false|true" policy="ANY|ALL|NONE">
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.
<wrapper prefix="AName" class="AClassName" checkactive="true|false" tenant="aTenant"/>
Caution | |
---|---|
Note: The tag name |
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.
|
|||
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>
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>
The configuration of AJAX / webservices is described in the corresponding section.
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>
Pustefix is based on Spring MVC and uses its DispatcherServlet to bootstrap the ApplicationContext on webapp startup. It configures its own ApplicationContext implementation by passing the PustefixWebApplicationContext
class as contextClass
init parameter in the webapp's web.xml
configuration file.
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.
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.
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).
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>
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. |
The default logging framework used by Pustefix is Log4j. If you want to stay with this default, Pustefix helps you setting up your logging. By default it reads the Log4j configuration
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).
Table of Contents
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” |
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.
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.
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.
Pustefix provides tags that allow you create links to internal and external pages.
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 |
It is possible to use the same children to control the submit behaviour as it is done with form controls.
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 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.
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.
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>
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.
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 | |
---|---|
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 | |
---|---|
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.
|
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.
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 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 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
|
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. |
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>
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>
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>)”).
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:
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="..."/>
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.
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>
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> <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.
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}"
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.
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 |
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
|
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>
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 |
|
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 |
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 |
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 |
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>
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.
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. |
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. |
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 | |
---|---|
If you don't give any selected wrappers for submit handling (
If you don't give any selected wrappers for retrieving data after successful submits ( 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 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).
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. |
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])
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 |
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 |
class | optional |
See Section 5.5.4, “Form elements” for an explanation on how the CSS class(es) for the html element are constructed. |
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.
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".
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.
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 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 |
class | optional |
See Section 5.5.4, “Form elements” for an explanation on how the CSS class(es) for the html element are constructed. |
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 |
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 |
All other attributes are copied to the resulting HTML element.
Errors in Pustefix come in two variants:
Field errors that are attached to a form input field and which must be handled on the page the form element is defined on.
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.
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.
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}
.
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>
Pustefix also provides several utility tags, that might be helpful in your application.
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. |
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).
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 | |
---|---|
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. |
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">
.
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>
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 accessible0
, 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 visited0
, if the page hasn't been visited yet-1
, if the page isn't configured
Note | |
---|---|
You should be aware that doing a boolean check on results of type |
Function: string pfx:getDisplayPageName()
The pfx:getDisplayPageName function returns the display name of the current page.
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 foundfalse
, 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>
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.
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.
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.
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')"/>
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, ERRORtest
or devel
=> DEBUG, 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>
Table of Contents
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
HttpRequestHandler
s 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.
Figure 6.1, “Pustefix HTTP request handlers” shows some of the HttpRequestHandler
s
that are already provided by the core framework
and should be sufficient for most of your needs when building a web application with Pustefix.
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.
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.
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
.
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.
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.
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.
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.
The situation is different for
State
s; 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.
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
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 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 |
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 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 |
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 |
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 |
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 |
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.
If the current page is a member of exactly one page flow, this flow will become the current page flow.
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.
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.
If the current page is not a member of any flow, the current page flow remains unset (the currentpageflow
variable remains null
).
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.
The first action to take is calling getDocument(...)
on the State
associated with the current page.
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
.
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.
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.
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!).
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.
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.
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.
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).
The first is de.schlund.pfixcore.workflow.app.StaticState
literal>. 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.
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).
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:
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:
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.
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).
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.
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.
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.
Write introduction...
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>
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
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); }
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); }
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.
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); }
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(); }
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; }
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
.
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.
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 | |
---|---|
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 | |
---|---|
|
Table of Contents
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.
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).
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.
Variants can influence the way an application works:
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
.
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 | |
---|---|
You must give a Each of the |
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 | |
---|---|
You must give a Each of the |
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.
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.
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
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.
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 | |
---|---|
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 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");
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:
projects/myproject/path/to/resource
projects/common/path/to/resource
PUSTEFIX-INF/path/to/resource
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 | |
---|---|
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 | |
---|---|
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. |
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
.
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
.
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 | |
---|---|
In addition to the |
<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>
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>
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.
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.
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 | |
---|---|
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 |
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.
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>
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.
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.
Doing asynchronous webservice calls, you can choose between two different webservice callback mechanisms.
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
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);
Supported Java types are:
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:
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.
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.
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) {...} }
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>
Changing the tag name for map entries | |
---|---|
The tag name, that is used for the entries in the map can be changed using the
|
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.
Pustefix already provides several XML serializers for common serialization tasks.
Simple serializers serialize scalar values (like strings, numbers or booleans). They are added as a new attribute on the current tag.
Complex serializers are used to serialize complex data structures. These always result in new tags that are being added to the document.
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”.
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>
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>
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; } ... }
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)
.
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.
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>
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 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 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.
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.
Scripted Flows are programmed using assorted statements which are explained here:
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.
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).
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.
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.
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.
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.
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.
Consider these questions:
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.
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.
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>
The following rules have to be considered, when scripting IHandlers and States alike.
de.schlund.pfixcore.generator.IHandler
and/or
de.schlund.pfixcore.workflow.State
)
as functions inside your scripts.
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.
There are two alternatives for defining the location of your script file.
/
(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.
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.
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()); } }
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.
Table of Contents
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).
Spring MVC's request mapping and parameter binding 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>
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:
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.
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)
.
Table of Contents
Modules allow you to share functionality, configuration options and view elements between different Pustefix applications.
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.
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.
Table of Contents
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.
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
.
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.
Table of Contents
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:
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. |
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.
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.
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>
Table of Contents
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).
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.
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.
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
.
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.
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.
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.
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.
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.
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
.
Table of Contents
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”).
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.
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.
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).
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”).
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”).
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).
Pustefix 0.16.0 focuses on SEO. It brings more user and search engine friendly URLs. The main features are:
New session tracking strategies:
URL cleanup
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.
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”).
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.
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.
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”).
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”).
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”).
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”).
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”).
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).
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.
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.
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)”).
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”).
Added basic support for loading resources from content management systems using CMIS/AtomPub.
Added XPath function to get current display page name during rendering (see the section called “Page functions”).
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>)”).
Added support for using custom XML attributes at page elements within the sitemap configuration (see Section 4.3.4, “Sitemap configuration”).
Added support for getting environment from within XSLT using the XPath function pfx:getEnvProperty
(see Section 5.7.1, “XPath functions”).
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.
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”).
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)”).
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”).
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”).
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”).
More Pustefix templates now support using include parameter expressions in attribute values (see Section 5.3.6, “Include parameters (<pfx:includeparam>)”).
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 ”).
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”).
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”).
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”).
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”).
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”).
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”).
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”).
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.
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”).
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.
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”).
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.
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”).
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”).
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.
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.1, “Upgrading from Pustefix 0.18 to 0.19”).
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).
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.
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).
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”).
Added support for rendering pages with non-200 status code (see SPDocument.setResponseStatus()
) and
overriding declared error pages (see SPDocument.setResponseErrorPageOverride()
).
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”).
Added support for using Google Closure Compiler to compress inline Javascript (default compressor will be the YUI compressor, if available in the classpath).
Improved rendering perforamnce (by extension function caching).
Added new DefaultIWrapperState
method handleWrapperErrors
for processing validation errors,
e.g. to log them.
Added new Context
method invalidateSessionAfterCompletion
for guaranteed invalidation of
session after current request has been processed.
Improved rendering performance for pages with lots of runtime includes (by include extension function caching).
Added servlet filter for performance logging.
Improved rendering performance for pages with lots of internal links (by caching callbacks).
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.