The Usecase Framework
- Introduction
- Directory Structure
- Architecture
- The Contract Between Flowscript And Usecase Handler
- Implementing a Custom Usecase
- Overriding Core Usecases in Publications
Introduction
A usecase in Lenya means a user triggered action. In most cases, a usecase is triggered by a CMS menu option on a specific document of the publication. This document is the object of the usecases' action (such as edit, delete, publish, ...).
There are usecases which are independent of a specific document, such as the ac.logout
usecase. In that case it does
not matter on what document the usecase is triggered. The part of the request which specifies the document is simply ignored
by usecases that are document independent.
The CMS menus trigger usecases by setting the lenya.usecase
request parameter on the current document. If for example
the user selects the Publish option from the Workflow menu, a request will be triggered such as:
GET http://www.server.com/lenya/default/authoring/tutorial.html?&lenya.usecase=publish
The Lenya global-sitemap.xmap
will redirect requests with a lenya.usecase
request parameter to the lenya/usecase.xmap
sitemap. This sitemap is used to recognonize usecases which are implemented in Java using the new Lenya 2.0 usecase framework:
<map:pipeline> <map:match type="registered-usecase"> <map:mount src="fallback://lenya/modules/usecase/usecase.xmap" uri-prefix="" check-reload="yes" reload-method="synchron"/> </map:match> </map:pipeline>
The registered-usecase
matcher's default implementation
(org.apache.lenya.cms.cocoon.matching.UsecaseRegistrationMatcher
) will use the Avalon
component resolver mechanism to resolve the name of the usecase to a an Avalon component. In case it cannot
resolve the usecase to an Avalon component, sitemap processing will continue and the usecase is treated in the
traditional way using the usecase
and step
matchers
(org.apache.cocoon.matching.WildcardRequestParameterMatcher
). In order for this to work
correctly, there should be a lenya.step
parameter in the request.
If the usecase could be resolved successfully into an Avalon component, processing will continue in the
sitemap.xmap
of the usecase module with the new JX and Java based 2.0 usecase framework.
The usecase framework in Lenya 2.0 is a simple framework to implement usecases using JX templates and Java. This approach is an "85% solution". It enables the user to implement a big range of common usecases.
Some special complex usecases might require a custom flowscript, in this case you can't use this framework.
Directory Structure
It is recommended to keep all usecases in modules for better reusability.
$MODULE_HOME /java /src Usecase handler classes and related classes /test Unit tests /usecases Usecase-related files /**.jx JX templates or usecase views /sitemap.xmap Callback pipelines
Architecture
A usecase request - denoted by the request parameter lenya.usecase
- is dispatched by the modules-core/usecase/sitemap.xmap
.
All usecases are handled by a single flowscript modules-core/usecase/usecases.js
. This keeps javascript maintenance costs at a minimum.
The flowscript usecases.js
determines the usecase handler class using the org.apache.lenya.cms.usecase.UsecaseResolver
.
All business code operations are delegated to the usecase handler class.
The Contract Between Flowscript And Usecase Handler
The usecase handler class has to implement the interface org.apache.lenya.cms.usecase.Usecase
.
The methods of this interface are called in a certain order when the usecase is invoked:
-
setSourceURL(String sourceUrl)
setName(String)
Initialize the handler.
-
Passing request parameters
In the next step, all request parameters are passed as parameters to the usecase, except the following reserved parameters:
-
lenya.usecase
-
lenya.continutation
-
submit
If the request parameter value is a string, the value can be accessed inside the usecase handler using
getParameterAsString(name)
. If the request parameter is a part of a multipart request, e.g., in a file upload form, it will be available usinggetPart(name)
.
-
-
checkPreconditions()
This method is called to check the pre-conditions of the usecase. The pre-conditions are checked before the usecase is started, i.e., before the first screen is presented to the user. To denote that a condition does not comply, add an appropriate error message using
addErrorMessage(String)
. If an error message was added, the usecase is not started. Alternatively, you can provide information to the user usingaddInfoMessage(String)
. This doesn't prevent the usecase from being executed.
-
lockInvolvedObjects()
This method is called to lock all objects which could be changed during the usecase.
The following methods are called in a loop while the user interacts with the usecase,
until a request parameter with the name submit
was sent.
-
getView()
Requests the next view to be displayed. The view may be
null
if no screen should be presented to the user.
-
advance()
This method is called to advance the usecase after the a user interaction. In contrast to
execute()
, this method is not called when the<input type="submit" name="submit">
was pressed, but for every other submitting of the form. A typical usecase is the multiple forms editor whereadvance()
is used to update the document when the user switched to another element.
When the form is submitted using the <input type="submit" name="submit">
button, the usecase is finished:
-
checkExecutionConditions()
This method is called before the usecase is actually executed. A typical example is the validation of form data.
-
execute()
This method actually executes the final step of the usecase.
When the form is submitted using the <input type="submit" name="cancel">
button, the usecase is cancelled:
-
cancel()
This method cancels the usecase. The transaction is rolled back.
Implementing a Custom Usecase
Prerequisites
- Choose a name to identify the usecase, e.g. editHeadline. The convention for usecase names is {module}.{usecase}, for instance article.editHeadline for the editHeadline usecase in the article module.
Add a Menu Item
This step is necessary if you want to call the usecase from the Lenya menubar.
- Add the corresponding menu item:
<item uc:usecase="article.editHeadline">Edit Headline</item>
Implement the Usecase Handler Class
- Choose a name for your business logic class, e.g.
org.myproject.lenya.article.EditHeadline
. - The class must implement the interface
org.apache.lenya.cms.usecase.Usecase
. -
To simplify development, you can extend one of the following classes:
-
org.apache.lenya.cms.usecase.AbstractUsecase
-
org.apache.lenya.cms.usecase.DocumentUsecase
(only for usecases invoked on document pages) -
org.apache.lenya.cms.usecase.SiteUsecase
-
-
Add the usecase handler class declaration to an XPatch file, e.g.
$PUB_HOME/config/cocoon-xconf/usecases.xconf
:<xconf xpath="/cocoon/usecases" unless="/cocoon/usecases/component-instance[@name = 'article.editHeadline']"> <component-instance name="article.editHeadline" logger="lenya.usecases.editHeadline" class="org.myproject.lenya.article.EditHeadline"/> </xconf>
Implement the View
The view of a usecase is optional. If you omit the view declaration, no screen is presented to the user. The view is declared in the usecase configuration:
<component-instance …> <view uri="modules/article/usecases/editHeadline.jx" menu="false"> <parameter name="title" value="Edit Headline"/> <parameter name="…" value="…"/> </view> </component-instance>
The <view>
element takes an optional menu
attribute
which denotes if the menubar should be visible when the usecase screen is presented.
If omitted, it defaults to false
.
The <view>
element can contain an arbitrary number of <parameter>
elements, each containing a name
and value
attribute. These
parameters can be accessed in the JX template using
${usecase.getView().getParameter('...')}
.
One option is to implement view for a usecase as a JX template. The location of the
JX template is defined using the <view>
element's uri
attribute (relatively to the fallback://lenya/
base URI).
The output of the template has to be a Lenya page:
<page:page xmlns:jx="http://apache.org/cocoon/templates/jx/1.0" xmlns:page="http://apache.org/cocoon/lenya/cms-page/1.0" xmlns="http://www.w3.org/1999/xhtml" xmlns:i18n="http://apache.org/cocoon/i18n/2.1" > <page:title> <i18n:text><jx:out value="${usecase.getView().getParameter('title')}"/></i18n:text> </page:title> <page:body> <form> <input type="hidden" name="lenya.continuation" value="${continuation.id}"/> <input type="hidden" name="lenya.usecase" value="${usecase.getName()}"/> ... </form> </page:body> </page:page>
Take care of adding the hidden lenya.usecase
and lenya.continuation
fields as shown above.
If you don't want to display a form, but output arbitrary XML, text, an SVG-generated image
or something like that, you can use a uri
attribute instead of the template
attribute. In this case, the menu
attribute can be omitted.
<component-instance …> <view uri="cocoon://modules/reports/generateReport.pdf" /> </component-instance>
Displaying Usecases in Tabs
It is possible to use a tab-based layout to assemble a set of usecases. The admin and site areas are displayed using this style. To add a tab-based usecase to a tab set, two steps are required:
-
Add the tab to the GUI manager configuration:
<xconf xpath="/cocoon/gui-manager/tab-group[@name = 'admin']" unless="/cocoon/gui-manager/tab-group[@name = 'admin']/tab[@name = 'search']"> <tab name="search" label="Search" usecase="admin.search"/> </xconf>
-
Add the tab configuration to the usecase view declaration:
<component-instance name="admin.search" logger="lenya.admin" class="org.apache.lenya.cms.usecase.DummyUsecase"> <view uri="modules/admin/usecases/search.jx" menu="true"> <tab group="admin" name="search"/> </view> <exit usecase="admin.search"/> </component-instance>
Dynamically Setting the Exit URL
Like you see above you can specify the "exit" URL of the usecase via the component configuration, however this is sometimes not enough because you may need to set the exit URL of a usecase dynamically.
Consider the use case is that you have a form and you need two possibilities to exit the usecase.
- Pressing on "submit" will save and exit to the same page in the authoring area.
- The other exit point could be a dynamic link based on a
<a href=""/>
where the user get redirected after submitting the form (e.g. to edit the linked document with the BXE editor).
First you need to add another request parameter (e.g.,
TRANSFER_FIELD
) denoting that the user clicked the link. This
could be e.g. a hidden field which contains the URL to transfer
the request. Further you need to override
Usecase.getTargetURL()
to return the BXE usecase URL if the
TRANSFER_FIELD
parameter is set, and super.getTargetURL()
otherwise.
public String getTargetURL(boolean success) { String tmpTransfer = getParameterAsString("TRANSFER_FIELD", null); if (!tmpTransfer.equals("") & tmpTransfer != null) return tmpTransfer; else return super.getTargetURL(success); }
Overriding Core Usecases in Publications
Overriding Usecase Handler Classes
The usecase resolver, which is responsible for obtaining the handler class for a usecase,
looks first if the current publication overrides the core usecase handler.
This can be done by declaring a usecase called <pub-id>/<usecase-name>
,
for instance mypub/admin.addUser
. To implement a core usecase using a custom
handler class, you need to
- Implement the handler class and put it in
$PUB_HOME/java/src
. In most cases, you will extend the core usecase handler class to inherit the basic functionality. -
Declare it in an xpatch file, for instance
$PUB_HOME/config/cocoon-xconf/usecases.xconf
:<xconf xpath="/cocoon/usecases" unless="/cocoon/usecases/component-instance[@name = 'mypub/admin.addUser']"> <component-instance name="mypub/admin.addUser" logger="lenya.usecases.editHeadline" class="org.myproject.lenya.usecases.AddUser"/> </xconf>
Now, when the usecase is invoked from inside the publication mypub, the custom handler class will be used.
Overriding JX Templates
Overriding the JX template of a usecase follows the
publication templating principle.
You just have to put a JX template with the same name in $PUB_HOME/lenya/usecases
,
for instance $PUB_HOME/lenya/usecases/admin/addUser.jx
.