SeamFramework.orgCommunity Documentation
Complementing the contextual component model, there are two further basic concepts that facilitate the extreme loose-coupling that is the distinctive feature of Seam applications. The first is a strong event model where events may be mapped to event listeners via JSF-like method binding expressions. The second is the pervasive use of annotations and interceptors to apply cross-cutting concerns to components which implement business logic.
The Seam component model was developed for use with event-driven applications, specifically to enable the development of fine-grained, loosely-coupled components in a fine-grained eventing model. Events in Seam come in several types, most of which we have already seen:
JSF events
jBPM transition events
Seam page actions
Seam component-driven events
Seam contextual events
All of these various kinds of events are mapped to Seam components via JSF EL method binding expressions. For a JSF event, this is defined in the JSF template:
<h:commandButton value="Click me!" action="#{helloWorld.sayHello}"/>
For a jBPM transition event, it is specified in the jBPM process definition or pageflow definition:
<start-page name="hello" view-id="/hello.xhtml">
<transition to="hello">
<action expression="#{helloWorld.sayHello}"/>
</transition>
</start-page>
You can find out more information about JSF events and jBPM events elsewhere. Let's concentrate for now upon the two additional kinds of events defined by Seam.
A Seam page action is an event that occurs just before we render a page.
We declare page actions in WEB-INF/pages.xml
. We
can define a page action for either a particular JSF view id:
<pages>
<page view-id="/hello.xhtml" action="#{helloWorld.sayHello}"/>
</pages>
Or we can use a *
wildcard as a suffix to the
view-id
to specify an action that applies to all
view ids that match the pattern:
<pages>
<page view-id="/hello/*" action="#{helloWorld.sayHello}"/>
</pages>
Keep in mind that if the <page>
element is defined in
a fine-grained page descriptor, the view-id
attribute
can be left off since it is implied.
If multiple wildcarded page actions match the current view-id, Seam will call all the actions, in order of least-specific to most-specific.
The page action method can return a JSF outcome. If the outcome is non-null, Seam will use the defined navigation rules to navigate to a view.
Furthermore, the view id mentioned in the <page>
element need not correspond to a real JSP or Facelets page! So, we can
reproduce the functionality of a traditional action-oriented framework
like Struts or WebWork using page actions. This is quite useful if you want
to do complex things in response to non-faces requests (for example, HTTP GET
requests).
Multiple or conditional page actions my be specified using the <action>
tag:
<pages>
<page view-id="/hello.xhtml">
<action execute="#{helloWorld.sayHello}" if="#{not validation.failed}"/>
<action execute="#{hitCount.increment}"/>
</page>
</pages>
Page actions are executed on both an initial (non-faces) request and a postback (faces) request. If you are using the page action to load data, this operation may conflict with the standard JSF action(s) being executed on a postback. One way to disable the page action is to setup a condition that resolves to true only on an initial request.
<pages>
<page view-id="/dashboard.xhtml">
<action execute="#{dashboard.loadData}"
if="#{not facesContext.renderKit.responseStateManager.isPostback(facesContext)}"/>
</page>
</pages>
This condition consults the ResponseStateManager#isPostback(FacesContext)
to
determine if the request is a postback. The ResponseStateManager is accessed using
FacesContext.getCurrentInstance().getRenderKit().getResponseStateManager()
.
To save you from the verbosity of JSF's API, Seam offers a built-in condition that allows you to
accomplish the same result with a heck of a lot less typing. You can disable a page action on postback
by simply setting the on-postback
to false
:
<pages>
<page view-id="/dashboard.xhtml">
<action execute="#{dashboard.loadData}" on-postback="false"/>
</page>
</pages>
For backwards compatibility reasons, the default value of the on-postback
attribute
is true, though likely you will end up using the opposite setting more often.
A JSF faces request (a form submission) encapsulates both an "action" (a method binding) and "parameters" (input value bindings). A page action might also needs parameters!
Since GET requests are bookmarkable, page parameters are passed as human-readable request parameters. (Unlike JSF form inputs, which are anything but!)
You can use page parameters with or without an action method.
Seam lets us provide a value binding that maps a named request parameter to an attribute of a model object.
<pages>
<page view-id="/hello.xhtml" action="#{helloWorld.sayHello}">
<param name="firstName" value="#{person.firstName}"/>
<param name="lastName" value="#{person.lastName}"/>
</page>
</pages>
The <param>
declaration is bidirectional, just
like a value binding for a JSF input:
When a non-faces (GET) request for the view id occurs, Seam sets the value of the named request parameter onto the model object, after performing appropriate type conversions.
Any <s:link>
or <s:button>
transparently includes the request parameter. The value of the parameter is
determined by evaluating the value binding during the render phase (when the
<s:link>
is rendered).
Any navigation rule with a <redirect/>
to
the view id transparently includes the request parameter. The value
of the parameter is determined by evaluating the value binding at
the end of the invoke application phase.
The value is transparently propagated with any JSF form submission
for the page with the given view id. This means that view parameters
behave like PAGE
-scoped context variables for
faces requests.
The essential idea behind all this is that however
we get from any other page to /hello.xhtml
(or from
/hello.xhtml
back to /hello.xhtml
),
the value of the model attribute referred to in the value binding is
"remembered", without the need for a conversation (or other server-side
state).
If just the name
attribute is specified then the
request parameter is propagated using the PAGE
context
(it isn't mapped to model property).
<pages>
<page view-id="/hello.xhtml" action="#{helloWorld.sayHello}">
<param name="firstName" />
<param name="lastName" />
</page>
</pages>
Propagation of page parameters is especially useful if you want to build multi-layer master-detail CRUD pages. You can use it to "remember" which view you were previously on (e.g. when pressing the Save button), and which entity you were editing.
Any <s:link>
or <s:button>
transparently propagates the request parameter if that parameter is listed
as a page parameter for the view.
The value is transparently propagated with any JSF form submission
for the page with the given view id. (This means that view parameters
behave like PAGE
-scoped context variables for
faces requests.
This all sounds pretty complex, and you're probably wondering if such an exotic construct is really worth the effort. Actually, the idea is very natural once you "get it". It is definitely worth taking the time to understand this stuff. Page parameters are the most elegant way to propagate state across a non-faces request. They are especially cool for problems like search screens with bookmarkable results pages, where we would like to be able to write our application code to handle both POST and GET requests with the same code. Page parameters eliminate repetitive listing of request parameters in the view definition and make redirects much easier to code.
Rewriting occurs based on rewrite patterns found for views in pages.xml
.
Seam URL rewriting does both incoming and outgoing URL rewriting based on the same pattern.
Here's a simple pattern:
<page view-id="/home.xhtml">
<rewrite pattern="/home" />
</page>
In this case, any incoming request for /home
will be sent to
/home.xhtml
. More interestingly,
any link generated that would normally point to /home.seam
will
instead be rewritten as /home
. Rewrite patterns only match the portion of the URL
before the query parameters. So, /home.seam?conversationId=13
and
/home.seam?color=red
will both be matched by this rewrite rule.
Rewrite rules can take these query paramters into consideration, as shown with the following rules.
<page view-id="/home.xhtml">
<rewrite pattern="/home/{color}" />
<rewrite pattern="/home" />
</page>
In this case, an incoming request for /home/red
will be served as
if it were a request
for /home.seam?color=red
. Similarly, if color is a page parameter an outgoing
URL that would normally show as /home.seam?color=blue
would instead
be output as
/home/blue
. Rules are processed in order, so it is important to list
more specific rules before more general rules.
Default Seam query parameters can also be mapped using URL rewriting, allowing for another
option for hiding Seam's fingerprints.
In the following example, /search.seam?conversationId=13
would
be written as /search-13
.
<page view-id="/search.xhtml">
<rewrite pattern="/search-{conversationId}" />
<rewrite pattern="/search" />
</page>
Seam URL rewriting provides simple, bidirectional rewriting on a per-view basis. For more
complex rewriting rules that cover non-seam components, Seam applications can continue to
use the org.tuckey URLRewriteFilter
or apply rewriting rules at the web server.
URL rewriting requires the Seam rewrite filter to be enable. Rewrite filter configuration is discussed in Section 31.1.3.3, “URL rewriting”.
You can specify a JSF converter for complex model properties:
<pages>
<page view-id="/calculator.xhtml" action="#{calculator.calculate}">
<param name="x" value="#{calculator.lhs}"/>
<param name="y" value="#{calculator.rhs}"/>
<param name="op" converterId="com.my.calculator.OperatorConverter" value="#{calculator.op}"/>
</page>
</pages>
Alternatively:
<pages>
<page view-id="/calculator.xhtml" action="#{calculator.calculate}">
<param name="x" value="#{calculator.lhs}"/>
<param name="y" value="#{calculator.rhs}"/>
<param name="op" converter="#{operatorConverter}" value="#{calculator.op}"/>
</page>
</pages>
JSF validators, and required="true"
may
also be used:
<pages>
<page view-id="/blog.xhtml">
<param name="date"
value="#{blog.date}"
validatorId="com.my.blog.PastDate"
required="true"/>
</page>
</pages>
Alternatively:
<pages>
<page view-id="/blog.xhtml">
<param name="date"
value="#{blog.date}"
validator="#{pastDateValidator}"
required="true"/>
</page>
</pages>
Even better, model-based Hibernate validator annotations are automatically recognized and validated. Seam also provides a default date converter to convert a string parameter value to a date and back.
When type conversion or validation fails, a global FacesMessage
is added to the FacesContext
.
You can use standard JSF navigation rules defined in faces-config.xml
in a Seam application. However, JSF navigation rules have a number of annoying
limitations:
It is not possible to specify request parameters to be used when redirecting.
It is not possible to begin or end conversations from a rule.
Rules work by evaluating the return value of the action method; it is not possible to evaluate an arbitrary EL expression.
A further problem is that "orchestration" logic gets scattered between pages.xml
and faces-config.xml
. It's better to unify this logic into pages.xml
.
This JSF navigation rule:
<navigation-rule>
<from-view-id>/editDocument.xhtml</from-view-id>
<navigation-case>
<from-action>#{documentEditor.update}</from-action>
<from-outcome>success</from-outcome>
<to-view-id>/viewDocument.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
Can be rewritten as follows:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<rule if-outcome="success">
<redirect view-id="/viewDocument.xhtml"/>
</rule>
</navigation>
</page>
But it would be even nicer if we didn't have to pollute our DocumentEditor
component with string-valued return values (the JSF outcomes). So Seam lets us write:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}"
evaluate="#{documentEditor.errors.size}">
<rule if-outcome="0">
<redirect view-id="/viewDocument.xhtml"/>
</rule>
</navigation>
</page>
Or even:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<rule if="#{documentEditor.errors.empty}">
<redirect view-id="/viewDocument.xhtml"/>
</rule>
</navigation>
</page>
The first form evaluates a value binding to determine the outcome value to be used by the subsequent rules. The second approach ignores the outcome and evaluates a value binding for each possible rule.
Of course, when an update succeeds, we probably want to end the current conversation. We can do that like this:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<rule if="#{documentEditor.errors.empty}">
<end-conversation/>
<redirect view-id="/viewDocument.xhtml"/>
</rule>
</navigation>
</page>
As we've ended conversation any subsequent requests won't know which document we are interested in. We can pass the document id as a request parameter which also makes the view bookmarkable:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<rule if="#{documentEditor.errors.empty}">
<end-conversation/>
<redirect view-id="/viewDocument.xhtml">
<param name="documentId" value="#{documentEditor.documentId}"/>
</redirect>
</rule>
</navigation>
</page>
Null outcomes are a special case in JSF. The null outcome is interpreted to mean "redisplay the page". The following navigation rule matches any non-null outcome, but not the null outcome:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<rule>
<render view-id="/viewDocument.xhtml"/>
</rule>
</navigation>
</page>
If you want to perform navigation when a null outcome occurs, use the following form instead:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<render view-id="/viewDocument.xhtml"/>
</navigation>
</page>
The view-id may be given as a JSF EL expression:
<page view-id="/editDocument.xhtml">
<navigation>
<rule if-outcome="success">
<redirect view-id="/#{userAgent}/displayDocument.xhtml"/>
</rule>
</navigation>
</page>
If you have a lot of different page actions and page parameters,
or even just a lot of navigation rules,
you will almost certainly want to split the declarations up over
multiple files. You can define actions and parameters for a page
with the view id /calc/calculator.xhtml
in a
resource named calc/calculator.page.xml
. The
root element in this case is the <page>
element, and the view id is implied:
<page action="#{calculator.calculate}">
<param name="x" value="#{calculator.lhs}"/>
<param name="y" value="#{calculator.rhs}"/>
<param name="op" converter="#{operatorConverter}" value="#{calculator.op}"/>
</page>
Seam components can interact by simply calling each others methods. Stateful components may even implement the observer/observable pattern. But to enable components to interact in a more loosely-coupled fashion than is possible when the components call each others methods directly, Seam provides component-driven events.
We specify event listeners (observers) in components.xml
.
<components>
<event type="hello">
<action execute="#{helloListener.sayHelloBack}"/>
<action execute="#{logger.logHello}"/>
</event>
</components>
Where the event type is just an arbitrary string.
When an event occurs, the actions registered for that event will be called
in the order they appear in components.xml
. How does a
component raise an event? Seam provides a built-in component for this.
@Name("helloWorld")
public class HelloWorld {
public void sayHello() {
FacesMessages.instance().add("Hello World!");
Events.instance().raiseEvent("hello");
}
}
Or you can use an annotation.
@Name("helloWorld")
public class HelloWorld {
@RaiseEvent("hello")
public void sayHello() {
FacesMessages.instance().add("Hello World!");
}
}
Notice that this event producer has no dependency upon event consumers. The event listener may now be implemented with absolutely no dependency upon the producer:
@Name("helloListener")
public class HelloListener {
public void sayHelloBack() {
FacesMessages.instance().add("Hello to you too!");
}
}
The method binding defined in components.xml
above
takes care of mapping the event to the consumer.
If you don't like futzing about in the components.xml
file, you can use an annotation instead:
@Name("helloListener")
public class HelloListener {
@Observer("hello")
public void sayHelloBack() {
FacesMessages.instance().add("Hello to you too!");
}
}
You might wonder why I've not mentioned anything about event objects in this discussion. In Seam, there is no need for an event object to propagate state between event producer and listener. State is held in the Seam contexts, and is shared between components. However, if you really want to pass an event object, you can:
@Name("helloWorld")
public class HelloWorld {
private String name;
public void sayHello() {
FacesMessages.instance().add("Hello World, my name is #0.", name);
Events.instance().raiseEvent("hello", name);
}
}
@Name("helloListener")
public class HelloListener {
@Observer("hello")
public void sayHelloBack(String name) {
FacesMessages.instance().add("Hello #0!", name);
}
}
Seam defines a number of built-in events that the application can use to perform special kinds of framework integration. The events are:
org.jboss.seam.validationFailed
— called when JSF validation fails
org.jboss.seam.noConversation
— called when there is no long running conversation and a long running conversation is required
org.jboss.seam.preSetVariable.<name>
— called when the context variable <name> is set
org.jboss.seam.postSetVariable.<name>
— called when the context variable <name> is set
org.jboss.seam.preRemoveVariable.<name>
— called when the context variable <name> is unset
org.jboss.seam.postRemoveVariable.<name>
— called when the context variable <name> is unset
org.jboss.seam.preDestroyContext.<SCOPE>
— called before the <SCOPE> context is destroyed
org.jboss.seam.postDestroyContext.<SCOPE>
— called after the <SCOPE> context is destroyed
org.jboss.seam.beginConversation
— called whenever a long-running conversation begins
org.jboss.seam.endConversation
— called whenever a long-running conversation ends
org.jboss.seam.conversationTimeout
— called when a conversation timeout occurs. The conversation id is passed as a parameter.
org.jboss.seam.beginPageflow
— called when a pageflow begins
org.jboss.seam.beginPageflow.<name>
— called when the pageflow <name> begins
org.jboss.seam.endPageflow
— called when a pageflow ends
org.jboss.seam.endPageflow.<name>
— called when the pageflow <name> ends
org.jboss.seam.createProcess.<name>
— called when the process <name> is created
org.jboss.seam.endProcess.<name>
— called when the process <name> ends
org.jboss.seam.initProcess.<name>
— called when the process <name> is associated with the conversation
org.jboss.seam.initTask.<name>
— called when the task <name> is associated with the conversation
org.jboss.seam.startTask.<name>
— called when the task <name> is started
org.jboss.seam.endTask.<name>
— called when the task <name> is ended
org.jboss.seam.postCreate.<name>
— called when the component <name> is created
org.jboss.seam.preDestroy.<name>
— called when the component <name> is destroyed
org.jboss.seam.beforePhase
— called before the start of a JSF phase
org.jboss.seam.afterPhase
— called after the end of a JSF phase
org.jboss.seam.postInitialization
— called when Seam has initialized and started up all components
org.jboss.seam.postReInitialization
— called when Seam has re-initialized and started up all components after a redeploy
org.jboss.seam.exceptionHandled.<type>
— called when an uncaught exception is handled by Seam
org.jboss.seam.exceptionHandled
— called when an uncaught exception is handled by Seam
org.jboss.seam.exceptionNotHandled
— called when there was no handler for an uncaught exception
org.jboss.seam.afterTransactionSuccess
— called when a transaction succeeds in the Seam Application Framework
org.jboss.seam.afterTransactionSuccess.<name>
— called when a transaction succeeds in the Seam Application Framework which manages an entity called <name>
org.jboss.seam.security.loggedOut
— called when a user logs out
org.jboss.seam.security.loginFailed
— called when a user authentication attempt fails
org.jboss.seam.security.loginSuccessful
— called when a user is successfully authenticated
org.jboss.seam.security.notAuthorized
— called when an authorization check fails
org.jboss.seam.security.notLoggedIn
— called there is no authenticated user and authentication is required
org.jboss.seam.security.postAuthenticate.
— called after a user is authenticated
org.jboss.seam.security.preAuthenticate
— called before attempting to authenticate a user
Seam components may observe any of these events in just the same way they observe any other component-driven events.
EJB 3.0 introduced a standard interceptor model for session bean components. To add an
interceptor to a bean, you need to write a class with a method annotated
@AroundInvoke
and annotate the bean with an
@Interceptors
annotation that specifies the name of the interceptor
class. For example, the following interceptor checks that the user is logged in before
allowing invoking an action listener method:
public class LoggedInInterceptor {
@AroundInvoke
public Object checkLoggedIn(InvocationContext invocation) throws Exception {
boolean isLoggedIn = Contexts.getSessionContext().get("loggedIn")!=null;
if (isLoggedIn) {
//the user is already logged in
return invocation.proceed();
}
else {
//the user is not logged in, fwd to login page
return "login";
}
}
}
To apply this interceptor to a session bean which acts as an action listener, we must
annotate the session bean @Interceptors(LoggedInInterceptor.class)
.
This is a somewhat ugly annotation. Seam builds upon the interceptor framework in
EJB3 by allowing you to use @Interceptors
as a meta-annotation for class
level interceptors (those annotated @Target(TYPE)
). In
our example, we would create an @LoggedIn
annotation, as follows:
@Target(TYPE)
@Retention(RUNTIME)
@Interceptors(LoggedInInterceptor.class)
public @interface LoggedIn {}
We can now simply annotate our action listener bean with @LoggedIn
to apply the interceptor.
@Stateless
@Name("changePasswordAction")
@LoggedIn
@Interceptors(SeamInterceptor.class)
public class ChangePasswordAction implements ChangePassword {
...
public String changePassword() { ... }
}
If interceptor ordering is important (it usually is), you can add
@Interceptor
annotations to your interceptor
classes to specify a partial order of interceptors.
@Interceptor(around={BijectionInterceptor.class,
ValidationInterceptor.class,
ConversationInterceptor.class},
within=RemoveInterceptor.class)
public class LoggedInInterceptor
{
...
}
You can even have a "client-side" interceptor, that runs around any of the built-in functionality of EJB3:
@Interceptor(type=CLIENT)
public class LoggedInInterceptor
{
...
}
EJB interceptors are stateful, with a lifecycle that is the same as the component
they intercept. For interceptors which do not need to maintain state, Seam lets
you get a performance optimization by specifying
@Interceptor(stateless=true)
.
Much of the functionality of Seam is implemented as a set of built-in Seam interceptors, including the interceptors named in the previous example. You don't have to explicitly specify these interceptors by annotating your components; they exist for all interceptable Seam components.
You can even use Seam interceptors with JavaBean components, not just EJB3 beans!
EJB defines interception not only for business methods (using @AroundInvoke
),
but also for the lifecycle methods @PostConstruct
, @PreDestroy
,
@PrePassivate
and @PostActive
. Seam supports all these
lifecycle methods on both component and interceptor not only for EJB3 beans, but also for
JavaBean components (except @PreDestroy
which is not meaningful for JavaBean
components).
JSF is surprisingly limited when it comes to exception handling. As a partial
workaround for this problem, Seam lets you define how a particular class of
exception is to be treated by annotating the exception class, or declaring
the exception class in an XML file. This facility is meant to be combined with
the EJB 3.0-standard @ApplicationException
annotation which
specifies whether the exception should cause a transaction rollback.
EJB specifies well-defined rules that let us control whether an exception
immediately marks the current transaction for rollback when it is thrown by
a business method of the bean: system exceptions always
cause a transaction rollback, application exceptions do
not cause a rollback by default, but they do if
@ApplicationException(rollback=true)
is specified. (An application exception is any checked exception, or any
unchecked exception annotated @ApplicationException
.
A system exception is any unchecked exception without an
@ApplicationException
annotation.)
Note that there is a difference between marking a transaction for rollback, and actually rolling it back. The exception rules say that the transaction should be marked rollback only, but it may still be active after the exception is thrown.
Seam applies the EJB 3.0 exception rollback rules also to Seam JavaBean components.
But these rules only apply in the Seam component layer. What about an exception that is uncaught and propagates out of the Seam component layer, and out of the JSF layer? Well, it is always wrong to leave a dangling transaction open, so Seam rolls back any active transaction when an exception occurs and is uncaught in the Seam component layer.
To enable Seam's exception handling, we need to make sure we have the master servlet
filter declared in web.xml
:
<filter>
<filter-name>Seam Filter</filter-name>
<filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Seam Filter</filter-name>
<url-pattern>*.seam</url-pattern>
</filter-mapping>
As the second requirement is to add web:exception-filter
configuration component
into WEB-INF/components.xml
. More details are in Section 31.1.3.1, “Exception handling”
You need to disable Facelets development mode in web.xml
too and
Seam debug mode in components.xml
if you want your exception handlers
to fire.
The following exception results in a HTTP 404 error whenever it propagates out of the Seam component layer. It does not roll back the current transaction immediately when thrown, but the transaction will be rolled back if it the exception is not caught by another Seam component.
@HttpError(errorCode=404)
public class ApplicationException extends Exception { ... }
This exception results in a browser redirect whenever it propagates out of the Seam component layer. It also ends the current conversation. It causes an immediate rollback of the current transaction.
@Redirect(viewId="/failure.xhtml", end=true)
@ApplicationException(rollback=true)
public class UnrecoverableApplicationException extends RuntimeException { ... }
You can also use EL to specify the viewId
to redirect to.
This exception results in a redirect, along with a message to the user, when it propagates out of the Seam component layer. It also immediately rolls back the current transaction.
@Redirect(viewId="/error.xhtml", message="Unexpected error")
public class SystemException extends RuntimeException { ... }
Since we can't add annotations to all the exception classes we are interested in,
Seam also lets us specify this functionality in pages.xml
.
<pages>
<exception class="javax.persistence.EntityNotFoundException">
<http-error error-code="404"/>
</exception>
<exception class="javax.persistence.PersistenceException">
<end-conversation/>
<redirect view-id="/error.xhtml">
<message>Database access failed</message>
</redirect>
</exception>
<exception>
<end-conversation/>
<redirect view-id="/error.xhtml">
<message>Unexpected failure</message>
</redirect>
</exception>
</pages>
The last <exception>
declaration does not specify a class,
and is a catch-all for any exception for which handling is not otherwise specified
via annotations or in pages.xml
.
You can also use EL to specify the view-id
to redirect to.
You can also access the handled exception instance through EL, Seam places it in the conversation context, e.g. to access the message of the exception:
...
throw new AuthorizationException("You are not allowed to do this!");
<pages>
<exception class="org.jboss.seam.security.AuthorizationException">
<end-conversation/>
<redirect view-id="/error.xhtml">
<message severity="WARN">#{org.jboss.seam.handledException.message}</message>
</redirect>
</exception>
</pages>
org.jboss.seam.handledException
holds the nested exception that
was actually handled by an exception handler. The outermost (wrapper) exception is
also available, as org.jboss.seam.caughtException
.
For the exception handlers defined in pages.xml
, it is possible
to declare the logging level at which the exception will be logged, or to even
suppress the exception being logged altogether. The attributes log
and log-level
can be used to control exception logging. By setting
log="false"
as per the following example, then no log message will
be generated when the specified exception occurs:
<exception class="org.jboss.seam.security.NotLoggedInException" log="false">
<redirect view-id="/register.xhtml">
<message severity="warn">You must be a member to use this feature</message>
</redirect>
</exception>
If the log
attribute is not specified, then it defaults to true
(i.e. the exception will be logged). Alternatively, you can specify the log-level
to control at which log level the exception will be logged:
<exception class="org.jboss.seam.security.NotLoggedInException" log-level="info">
<redirect view-id="/register.xhtml">
<message severity="warn">You must be a member to use this feature</message>
</redirect>
</exception>
The acceptable values for log-level
are: fatal, error, warn, info, debug
or trace
. If the log-level
is not specified, or if an invalid value is
configured, then it will default to error
.
If you are using JPA:
<exception class="javax.persistence.EntityNotFoundException">
<redirect view-id="/error.xhtml">
<message>Not found</message>
</redirect>
</exception>
<exception class="javax.persistence.OptimisticLockException">
<end-conversation/>
<redirect view-id="/error.xhtml">
<message>Another user changed the same data, please try again</message>
</redirect>
</exception>
If you are using the Seam Application Framework:
<exception class="org.jboss.seam.framework.EntityNotFoundException">
<redirect view-id="/error.xhtml">
<message>Not found</message>
</redirect>
</exception>
If you are using Seam Security:
<exception class="org.jboss.seam.security.AuthorizationException">
<redirect>
<message>You don't have permission to do this</message>
</redirect>
</exception>
<exception class="org.jboss.seam.security.NotLoggedInException">
<redirect view-id="/login.xhtml">
<message>Please log in first</message>
</redirect>
</exception>
And, for JSF:
<exception class="javax.faces.application.ViewExpiredException">
<redirect view-id="/error.xhtml">
<message>Your session has timed out, please try again</message>
</redirect>
</exception>
A ViewExpiredException
occurs if the user posts back to a page once their session has
expired. The conversation-required
and no-conversation-view-id
settings in the Seam page descriptor, discussed in Section 8.4, “Requiring a long-running conversation”, give you
finer-grained control over session expiration if you are accessing a page used within a conversation.