Overview
Other MyFaces Extensions
- ExtVal
- Ext-Script
- [Orchestra]
- [Portlet Bridge]
Community
Development
Sponsorship
Your browser does not support iframes
Table of Contents | ||||||
---|---|---|---|---|---|---|
|
The Intro page provides an overview, the setup of this module and describes the motivation for the features described below. This page explains the most important APIs and mechanisms of the JSF module provided by CODI. Please note that this page doesn't show all possibilities. If you have any question, please contact the community!
...
The page CODI Modules provides an overview about CODI modules and how to add them to your project.
For using the features described in this page, you have to add the core, the message module and the JSF module for the JSF version you are using.
...
All CODI scopes have in common that they are bound to a window. A window is represented by the WindowContext
which stores all scopes and allows to control the window and its scopes in a fine-grained manner.
Besides this documentation http://www.slideshare.net/os890/myfaces-codi-conversations provides a basic overview.
First of all it's important to mention that CODI starts (grouped) conversations automatically as soon as you access conversation scoped beans. Furthermore, the invocation of Conversation#close
leads to an immediate termination of the conversation.
...
Note | ||
---|---|---|
| ||
Since the View-Access scope is just a different kind of a conversation |
Note | ||
---|---|---|
| ||
CODI conversations get closed/restarted immediately instead of keeping them until the end of the request like std. conversations do, because the behaviour of std. conversations breaks a lot of use-cases. However, if you really need to keep them until the end of the request, you can use EnhancedConversation#end which is provided by the https://bitbucket.org/os890/codi-addons/src/d2e11ac5e941/enhanced_conversations/. |
Instead of destroying the whole conversation the conversation stays active and only the scoped instances are destroyed. (The conversation will be marked as accessed.) As soon as an instance of a bean is requested, the instance Instead of destroying the whole conversation the conversation stays active and only the scoped instances are destroyed. (The conversation will be marked as accessed.) As soon as an instance of a bean is requested, the instance will be created based on the original bean descriptor. This approach allows a better performance, if the conversation is needed immediately (e.g. if you know in your action method that the next page will/might use the same conversation again).
...
Note | ||
---|---|---|
| ||
Compared to std. CDI conversations CODI provides completely different conversation concepts. "Just the name is the same." |
In case of conversations you have to un-scope beans manually (or they we be terminated automatically after a timeout). However, sometimes you need beans with a lifetime which is as long as needed and as short as possible - which are terminated automatically (as soon as possible). In such an use-case you can use this scope. The simple rule is, as long as the bean is referenced by a page - the bean will be available for the next page (if it's used again the bean will be forwarded again). It is important that it's based on the view-id of a page (it isn't based on the request) so e.g. Ajax requests don't trigger a cleanup if the request doesn't access all view-access scoped beans of the page. That's also the reason for the name @*View*AccessScoped.
...
Due to the parallel conversation concept of CODI there is no need of something like nested conversations. Just use them in parallel and terminate them in a fine-granular way as soon as you don't need them any longer. As described above, you can terminate a whole conversation-group. However, sometimes it's essential to have subgroups if you need to end just a part of an use-case instead of all beans related to an use-case. However, that isn't a replacement of sub-conversations, because a replacement isn't needed.
A sub-group is just a class or an interface used to identify a bunch of beans within a group. To terminate such a sub-group, it's just needed to pass the class/interface to the corresponding API for terminating a conversation. The sub-group gets detected autom. and instead of terminating a whole conversation-group, the beans of the sub-group get un-scoped.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
public class MyGroup{}
@ConversationScoped
@ConversationGroup(MyGroup.class)
public class BeanA {}
@ConversationScoped
@ConversationGroup(MyGroup.class)
public class BeanB {}
@ConversationScoped
@ConversationGroup(MyGroup.class)
public class BeanC {}
@ConversationSubGroup(subGroup = {BeanA.class, BeanB.class})
public class MySubGroup extends MyGroup {}
or
@ConversationSubGroup(of = MyGroup.class, subGroup = {BeanA.class, BeanB.class})
public class MySubGroup {}
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
this.windowContext.closeConversation(MySubGroup.class)
|
As you see the class/interface of the sub-group has to extend/implement the group or you specify it via the @ConversationSubGroup#of
. With @ConversationSubGroup#subGroup
you can list all beans which belong to the sub-group. If you have a lot of such beans or you would like to form (sub-)use-case oriented groups, you can use implicit groups:
...
@ViewAccessScoped
public class WizardBean implements Serializable
{
//...
}
The usage of this scope is very similar to normal conversations. Only the cleanup strategy is different and the concept itself doesn't need/support the usage of @ConversationGroup
.
The window-scope is like a session per window. That means that the data is bound to a window/tab and it not shared between windows (like the session scope does). Usually you need the window-scope instead of the session-scope. There aren't a lot of use-cases which need shared data between windows.
The usage of this scope is very similar to normal conversations. Only the cleanup strategy is different and the concept itself doesn't need/support the usage of @ConversationGroup
.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@WindowScoped public classinterface PreferencesBean implements Serializable { //... } |
Since WindowContext#closeConversations
doesn't affect window scoped beans we need a special API for terminating all window scoped beans.
If you don't use qualifiers for your window scoped beans, you can just inject the conversation into a window scoped bean and invoke the methods discussed above. If you don't have this constellation, you can use the WindowContext
to terminate the window scoped beans or the whole window context. If you terminate the whole window, you also destroy all conversation and view-access scoped beans automatically.
Wizard {}
@ConversationSubGroup(of = MyGroup.class, subGroup = Wizard.class)
public class ImplicitSubGroup
{
}
@Named("myWizard")
@ConversationScoped
@ConversationGroup(MyGroup.class)
public class WizardController implements Serializable, Wizard
{
//...
}
this.windowContext.closeConversationGroup(ImplicitSubGroup.class);
|
In the listing above all beans which implement the Wizard
interface will be closed as soon as you close the ImplicitSubGroup
.
As mentioned before the concept of sub-groups is no replacement for sub-conversations, because they aren't needed with the parallel conversation concept of CODI. In most cases you won't face the need to use sub-groups. However, in some cases they allow to handle the conversation-management in an easier way.
The window-scope is like a session per window. That means that the data is bound to a window/tab and it not shared between windows (like the session scope does). Usually you need the window-scope instead of the session-scope. There aren't a lot of use-cases which need shared data between windows.
The usage of this scope is very similar to normal conversations. Only the cleanup strategy is different and the concept itself doesn't need/support the usage of @ConversationGroup
.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@WindowScoped
public class PreferencesBean implements Serializable
{
//...
}
|
Since WindowContext#closeConversations
doesn't affect window scoped beans we need a special API for terminating all window scoped beans.
If you don't use qualifiers for your window scoped beans, you can just inject the conversation into a window scoped bean and invoke the methods discussed above. If you don't have this constellation, you can use the WindowContext
to terminate the window scoped beans or the whole window context. If you terminate the whole window, you also destroy all conversation and view-access scoped beans automatically.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
//...
public class CustomWindowControllerBean1 | ||||||
Code Block | java | java | ||||
title | Terminate the whole content of the window | |||||
//...
public class CustomWindowControllerBean1
{
@Inject
private WindowContext windowContext;
//...
public void closeWindow()
{
this.windowContext.close();
}
}
| ||||||
Code Block | ||||||
java | java | |||||
title | Terminate all window scoped beans | //... public class CustomWindowControllerBean2 { @Inject private WindowContext windowContext; //... public void finishcloseWindow() { this.windowContext.closeConversationGroupclose(WindowScoped.class); } } |
JSF 2.0 introduced new annotations as well as a new scope - the View Scope. CODI allows to use all the CDI mechanisms in beans annotated with:
javax.faces.bean.ApplicationScoped
javax.faces.bean.SessionScoped
javax.faces.bean.RequestScoped
javax.faces.bean.ViewScoped
Furthermore, the managed-bean annotation ( javax.faces.bean.ManagedBean
) is mapped to @Named
from CDI.
All these annotations are mapped automatically. So you won't face issues, if you import a JSF 2 annotation instead of the corresponding CDI annotation.
CODI provides a fine grained conversation scope with multiple parallel and isolated/independent conversations within a single window as well as a view-access scope (see above). So we (currently) don't think that we need a flash scope. Please contact us, if you find an use-case which needs the flash scope and you can't use the other CODI scopes. Other portable extensions (like Seam 3 btw. Seam-Faces) just provide this scope because they don't have such fine grained conversations.
CODI allows using @Inject within the following JSF (1.2 and 2.0) artifacts:
As soon as a converter or a validator is annotated with @Advanced it's possible to use @Inject.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
//...
public class CustomWindowControllerBean2
{
@Inject
private WindowContext windowContext;
//...
public void finish()
{
this.windowContext.closeConversationGroup(WindowScoped.class);
}
}
|
In case of conversations you have to un-scope beans manually (or they we be terminated automatically after a timeout). However, sometimes you need beans with a lifetime which is as long as needed and as short as possible - which are terminated automatically (as soon as possible). In such an use-case you can use this scope. The simple rule is, as long as the bean is referenced by a page - the bean will be available for the next page (if it's used again the bean will be forwarded again). It is important that it's based on the view-id of a page (it isn't based on the request) so e.g. Ajax requests don't trigger a cleanup if the request doesn't access all view-access scoped beans of the page. That's also the reason for the name @*View*AccessScoped.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@ViewAccessScoped
public class WizardBean implements Serializable
{
//...
}
|
The usage of this scope is very similar to normal conversations. Only the cleanup strategy is different and the concept itself doesn't need/support the usage of @ConversationGroup
.
Note | ||
---|---|---|
| ||
@ViewAccessScoped beans are best used in conjunction with the CODI ClientSideWindowHandler, which ensures a clean browser-tab separation without touching the old windowId. Otherwise a 'open in new tab' on a page with a @ViewAccessScoped bean might cause the termination (and re-initialization) of that bean. |
Our Rest Scope is a Conversation which intended for GET pages. On the first access to such a bean on a view which gets invoked via GET, all the f:viewParam will be parsed and stored internally. This RestScoped conversation automatically expires once the bean gets accessed via GET on another view or with a different set of f:viewParam.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@RestScoped
public class CarBean implements Serializable
{
//...
}
|
The usage of this scope is very similar to normal conversations. Only the cleanup strategy is different and the concept itself doesn't need/support the usage of @ConversationGroup
.
Note | ||
---|---|---|
| ||
Please note that the usage of Post-Redirect-GET (PRG), e.g. via faces-redirect=true, might lead to ending the conversation and thus will delete/re-initialize the bean. |
JSF 2.0 introduced new annotations as well as a new scope - the View Scope. CODI allows to use all the CDI mechanisms in beans annotated with:
javax.faces.bean.ApplicationScoped
javax.faces.bean.SessionScoped
javax.faces.bean.RequestScoped
javax.faces.bean.ViewScoped
Furthermore, the managed-bean annotation ( javax.faces.bean.ManagedBean
) is mapped to @Named
from CDI.
All these annotations are mapped automatically. So you won't face issues, if you import a JSF 2 annotation instead of the corresponding CDI annotation.
CODI provides a fine grained conversation scope with multiple parallel and isolated/independent conversations within a single window as well as a view-access scope (see above). So we (currently) don't think that we need a flash scope. Please contact us, if you find an use-case which needs the flash scope and you can't use the other CODI scopes. Other portable extensions (like Seam 3 btw. Seam-Faces) just provide this scope because they don't have such fine grained conversations.
All CODI scopes also support navigation via GET-Requests.
With JSF2 you can use GET-Requests with h:link
. It's recommended to use it instead of a plain HTML link.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<h:link value="My Page" outcome="myPage"/>
|
However, if you have a good reason for it and you really have to use a plain HTML link instead, you have to use something like:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<a href="#{facesContext.externalContext.request.contextPath}/myPage.xhtml?windowId=#{currentWindow.id}">My Page< /a >
|
CODI allows using @Inject within the following JSF (1.2 and 2.0) artifacts:
As soon as a converter or a validator is annotated with @Advanced it's possible to use @Inject.
Example for a validator:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Advanced
@FacesValidator("...") //use it just in case of JSF 2.0
public class DependencyInjectionAwareValidator implements Validator
{
@Inject
private CustomValidationService customValidationService;
public void validate(FacesContext facesContext, UIComponent uiComponent, Object value) throws ValidatorException
{
Violation violation = this.customValidationService.validate(value);
//...
}
}
|
Example for a converter:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Advanced
@FacesConverter("...") //use it just in case of JSF 2.0
public class DependencyInjectionAwareConverter implements Converter
{
@Inject
private OrderService orderService;
public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value)
throws ConverterException
{
if (value != null && value.length() > 0)
{
return this.orderService.loadByOrderNumber(value);
}
return null;
}
//...
}
|
As an alternative to a full phase-listener CODI allows to use observers as phase-listener methods.
Example:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
protected void observePreRenderView(@Observes @BeforePhase(RENDER_RESPONSE) PhaseEvent phaseEvent)
{
//...
}
|
If you would like to restrict the invocation to a specific view, it's possible to use the optional @View
annotation.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@View(DemoPage.class)
public void observePostInvokeApplication(@Observes @AfterPhase(JsfPhaseId.INVOKE_APPLICATION) PhaseEvent event)
{
//...
}
|
For further details about DemoPage.class
please have a look at the view-config section.
Note | ||
---|---|---|
| ||
If you don't need the |
Note | ||
---|---|---|
| ||
Since v0.9.1 |
CODI provides an annotation for phase-listeners:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@JsfPhaseListener
public class DebugPhaseListener implements PhaseListener
{
private static final Log LOG = LogFactory.getLog(DebugPhaseListener.class);
private static final long serialVersionUID = -3128296286005877801L;
public void beforePhase(PhaseEvent phaseEvent)
{
if(LOG.isDebugEnabled())
{
LOG.debug("before: " + phaseEvent.getPhaseId());
}
}
public void afterPhase(PhaseEvent phaseEvent)
{
if(LOG.isDebugEnabled())
{
LOG.debug("after: " + phaseEvent.getPhaseId());
}
}
public PhaseId getPhaseId()
{
return PhaseId.ANY_PHASE;
}
}
|
If you have to specify the order of phase-listeners you can us the optional @InvocationOrder
annotation.
In combination with @Advanced
it's possible to use dependency injection.
Example:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Advanced
@JsfPhaseListener
@InvocationOrder(1) //optional
public class DebugPhaseListener implements PhaseListener
{
@Inject
private DebugService debugService;
public void beforePhase(PhaseEvent phaseEvent)
{
this.debugService.log(phaseEvent);
}
public void afterPhase(PhaseEvent phaseEvent)
{
this.debugService.log(phaseEvent);
}
public PhaseId getPhaseId()
{
return PhaseId.ANY_PHASE;
}
}
|
Info | ||
---|---|---|
| ||
It's easier to use the annotation because there isn't an overlap with the name of the interface. So it isn't required to use the fully qualified name for one of them. |
Sometimes it's essential to perform logic directly after the FacesContext
started and/or directly before the FacesContext
gets destroyed. In such a case CODI provides the annotations @BeforeFacesRequest
and @AfterFacesRequest
.
ExampleExample for a validator:
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
protected void initFacesRequest(@Observes @BeforeFacesRequest FacesContext facesContext) { @Advanced @FacesValidator("...") //use it just in case of JSF 2.0 public class DependencyInjectionAwareValidator implements Validator { @Inject private CustomValidationService customValidationService; public void validate(FacesContext facesContext, UIComponent uiComponent, Object value) throws ValidatorException { Violation violation = this.customValidationService.validate(value); //... } } |
CODI offers a bunch of producers for JSF artifacts.
Example for a converter:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Advanced @FacesConverter("...") //use it just in case of JSF@Inject private FacesContext facesContext; |
Helper for detecting the current phase of the JSF request lifecycle.
Example:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
2.0 public class DependencyInjectionAwareConverter implements ConverterMyBean { @Inject private OrderServiceJsfLifecyclePhaseInformation orderServicephaseInformation; public Objectvoid getAsObject(FacesContext facesContext, UIComponent uiComponent, String value) throws ConverterException { if (value != null && value.length() > 0)execute() { { if return(this.phaseInformation.isProcessValidationsPhase() || this.orderServicephaseInformation.loadByOrderNumberisUpdateModelValuesPhase(value)); } return null;{ } //... } |
As an alternative to a full phase-listener CODI allows to use observers as phase-listener methods.
Example:
Code Block | |
---|---|
java | java |
title | Global observer method for phase-events | protected void observePreRenderView(@Observes @BeforePhase(RENDER_RESPONSE) PhaseEvent phaseEvent) { //... } } } } |
In some projects users are using enums which allow e.g. a type-safe navigation. CODI provides a mechanism which goes beyond that. You can use classes which hosts a bunch of meta-data. One use-case is to use these special classes for type-safe navigation. Beyond that CODI allows to provide further meta-data e.g. page-beans which are loaded before the rendering process starts, type-safe security and it's planned to add further features to this mechanism.
The following example shows a simple view-configIf you would like to restrict the invocation to a specific view, it's possible to use the optional @View
annotation.
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
@Page @View(DemoPage.class) public voidfinal observePostInvokeApplication(@Observes @AfterPhase(JsfPhaseId.INVOKE_APPLICATION) PhaseEvent event) { //... } |
For further details about DemoPage.class
please have a look at the view-config section.
Note | ||
---|---|---|
| ||
If you don't need the |
Note | ||
---|---|---|
| ||
Since v0.9.1 |
class Home implements ViewConfig
{
}
|
This mechanism works due to the naming convention. Instead of the convention it's possible to specify the name of the page manually. This feature is described at the end of this section.
Note | ||
---|---|---|
| ||
Use classes for your pages and for everything else interfaces! |
If you would like to group pages (you will learn some reasons for that later on), you can nest the classes.CODI provides an annotation for phase-listeners:
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
public interface Wizard extends ViewConfig @JsfPhaseListener public class DebugPhaseListener implements PhaseListener { private static final Log LOG = LogFactory.getLog(DebugPhaseListener.class); private static final long serialVersionUID = -3128296286005877801L; public void beforePhase(PhaseEvent phaseEvent) { if(LOG.isDebugEnabled()) { LOG.debug("before: " + phaseEvent.getPhaseId()); @Page } } public voidfinal afterPhase(PhaseEvent phaseEvent) {class Page1 implements Wizard if(LOG.isDebugEnabled()){ { LOG.debug("after: " + phaseEvent.getPhaseId());} }@Page } public final PhaseId getPhaseId() { class Page2 implements Wizard { return PhaseId.ANY_PHASE; } } |
If you have to specify the order of phase-listeners you can us the optional @InvocationOrder
annotation.
In combination with @Advanced
it's possible to use dependency injection.
Such a grouping allows to reduce the number of class files in your workspace. Furthermore modern IDEs allow to show the logical hierarchy out-of-the-box (in Intellij it's called "Type Hierarchy").
Note | ||
---|---|---|
| ||
At |
View configs allow to reflect your folder structure in the meta-classes.Example:
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
public interface Wizard extends ViewConfig { @Page public final class Step1 implements Wizard { @Advanced @JsfPhaseListener @InvocationOrder(1) //optional public class DebugPhaseListener implements PhaseListener { @Inject private DebugService debugService; public void beforePhase(PhaseEvent phaseEvent) { this.debugService.log(phaseEvent); } public void afterPhase(PhaseEvent phaseEvent) @Page { public this.debugService.log(phaseEvent); } public PhaseId getPhaseId() { return PhaseId.ANY_PHASE; } } |
Info | ||
---|---|---|
| ||
It's easier to use the annotation because there isn't an overlap with the name of the interface. So it isn't required to use the fully qualified name for one of them. |
Sometimes it's essential to perform logic directly after the FacesContext
started and/or directly before the FacesContext
gets destroyed. In such a case CODI provides the annotations @BeforeFacesRequest
and @AfterFacesRequest
.
...
final class Step2 implements Wizard
{
}
}
|
... leads to the following view-ids: /wizard/step1.xhtml
and /wizard/step2.xhtml
.
That means - if you rename the folder wizard, you just have to rename a single class and everything is in sync again.
Note | ||
---|---|---|
| ||
While the nested classes lead to the final path of the file, the inheritance allows to configure a bunch of pages. |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
protected void initFacesRequest(@Observes @BeforeFacesRequest FacesContext facesContext)
{
//...
}
|
CODI offers a bunch of producers for JSF artifacts.
Example:
...
@Inject
private FacesContext facesContext;
| |
@Page(navigation = REDIRECT)
public interface Wizard extends ViewConfig
{
@Page
public final class Step1 implements Wizard
{
}
@Page
public final class Step2 implements Wizard
{
}
}
|
... leads to redirects as soon as navigation is triggered and Step1
or Step2
are the targets. You can centralize all available configs. Some of them can be replaced at a more concrete level (in the example above it would be possible to override the redirect mode e.g. for Step1
or Step2
) whereas others will be aggregated (like AccessDecisionVoter
s}.
Note | ||
---|---|---|
| ||
Besides this naming convention you can use |
It's possible to use custom annotations in view-config classes. Just annotate the custom annotation with @ViewMetaData
.
Helper for detecting the current phase of the JSF request lifecycle.
Example:
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
@Target({TYPE}) @Retention(RUNTIME) @Documented @ViewMetaData public @interface ViewMode public class MyBean { @Inject private JsfLifecyclePhaseInformation phaseInformation; public void execute() { if (this.phaseInformation.isProcessValidationsPhase() || this.phaseInformation.isUpdateModelValuesPhase()) { //... } |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Page @ViewMode public final class Home implements } }ViewConfig { } |
In some projects users are using enums which allow Optionally you can specify if a nested class (e.g. a type-safe navigation. CODI provides a mechanism which goes beyond that. You can use classes which hosts a bunch of meta-data. One use-case is to use these special classes for type-safe navigation. Beyond that CODI allows to provide further meta-data e.g. page-beans which are loaded before the rendering process starts, type-safe security and it's planned to add further features to this mechanism.
The following example shows a simple view-config.
UseCase1.Step1
) overrides the meta-data. That means there will be just one instance per such a custom annotation per page. Per default all annotations are collected independent of the already found types and independent of the instance count of an annotation type.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Page
public final class Home implements ViewConfig
{
}
|
This mechanism works due to the naming convention. Instead of the convention it's possible to specify the name of the page manually. This feature is described at the end of this section.
...
| |
@Target({TYPE})
@Retention(RUNTIME)
@Documented
@ViewMetaData(override = true)
public @interface PageIcon
{
//...
}
|
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
@PageIcon(...) public abstractinterface classUseCase1 Wizard implementsextends ViewConfig { @Page //inherits the page-icon of the @Pageuse-case public final class Page1Step1 extendsimplements Wizard { } @Page @PageIcon(...) //overrides the page-icon of the use-case public final class Page2Step2 extendsimplements Wizard { } } |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Inject private ViewConfigResolver viewConfigResolver; this.viewConfigResolver.getViewConfigDescriptor(UseCase1.Step2.class).getMetaData(); //or something } } |
Such a grouping allows to reduce the number of class files in your workspace. Furthermore modern IDEs allow to show the logical hierarchy out-of-the-box (in Intellij it's called "Type Hierarchy").
Note | ||
---|---|---|
| ||
At |
like:
this.viewConfigResolver.getViewConfigDescriptor(this.facesContext.getViewRoot().getViewId()).getMetaData();
|
Type-safe view configs also allow to easily specify the valid page rangeView configs allow to reflect your folder structure in the meta-classes.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
public abstract interface PublicView extends ViewConfig {} @Page public class WizardReportPage implements PublicView {} //... @Secured(LoginAccessDecisionVoter.class) public interface Internal extends ViewConfig { @Page public finalclass classReportPage Step1implements extends Wizard { } Internal {} } //... public Class<? extends Internal> showInternalReport() { @Page//... public final class Step2 extends Wizardreturn Internal.ReportPage.class; } //... public Class<? extends PublicView> showPublicReport() { {//... return ReportPage.class; } } |
... leads to the following view-ids: /wizard/step1.xhtml
and /wizard/step2.xhtml
.
That means - if you rename the folder wizard, you just have to rename a single class and everything is in sync again.
Note | ||
---|---|---|
| ||
While the nested classes lead to the final path of the file, the inheritance allows to configure a bunch of pages. |
In this example it's easy to ensure that the correct target page is used. It isn't possible to expose the internal report (page) as public report by accident (due to a wrong import or a bad refactoring).
In the previous section you have learned some details about the view-config mechanism provided by CODI. You can use these meta-classes for the navigation.
Example:
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
public Class<? extends ViewConfig> navigateToHomeScreen() @Page(navigation = REDIRECT) public abstract class Wizard implements ViewConfig { @Page public final class Step1 extends Wizard { } @Page public final class Step2 extends Wizard { }return Home.class; } |
Note |
---|
... leads to redirects as soon as navigation is triggered and Step1
or Step2
are the targets. You can centralize all available configs. Some of them can be replaced at a more concrete level (in the example above it would be possible to override the redirect mode e.g. for Step1
or Step2
) whereas others will be aggregated (like AccessDecisionVoter
s}.
Note | ||
---|---|---|
| ||
Besides this naming convention you can use |
It's possible to use custom annotations in view-config classes. Just annotate the custom annotation with @ViewMetaData
.
...
@Target({TYPE})
@Retention(RUNTIME)
@Documented
@ViewMetaData
public @interface ViewMode
{
//...
}
...
@Page
@ViewMode
public final class Home implements ViewConfig
{
}
Optionally you can specify if a nested class (e.g. UseCase1.Step1
) overrides the meta-data. That means there will be just one instance per such a custom annotation per page. Per default all annotations are collected independent of the already found types and independent of the instance count of an annotation type.
...
@Target({TYPE})
@Retention(RUNTIME)
@Documented
@ViewMetaData(override = true)
public @interface PageIcon
{
//...
}
...
@PageIcon(...)
public abstract class UseCase1 implements ViewConfig
{
@Page
//inherits the page-icon of the use-case
public final class Step1 extends Wizard
{
}
@Page
@PageIcon(...) //overrides the page-icon of the use-case
public final class Step2 extends Wizard
{
}
}
| ||
Some EL implementations like JUEL check the allowed return type explicitly. In combination with early implementations of Facelets you might see an exception which tells that action methods have to return strings. In such a case you can use |
Info | ||
---|---|---|
| ||
The new API of JSF 2 |
Since JSF 2.0 it's possible to use GET requests for navigating between pages. With MyFaces CODI you can use type-safe navigation also in combination with GET-Requests.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<!-- Std. approach with JSF 2.0: -->
<h:button value="..." outcome="/pages/myPage.xhtml">
<f:param name="param1" value="v1"/>
</h:button>
<!-- CODI allows to use: -->
<h:button value="..." outcome="#{myController.myPage}">
<f:param name="param1" value="v1"/>
</h:button>
<!-- or if needed (not recommended, because it isn't type-safe): -->
<h:button value="..." outcome="class myPackage.Pages.MyPage">
<f:param name="param1" value="v1"/>
</h:button>
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
public Class<? extends ViewConfig> getMyPage()
{
return Pages.MyPage.class;
}
|
In case of type-safe navigation it's possible to observe navigations which have a view-config for the source-view as well as the target-view.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
protected void onViewConfigNavigation(@Observes PreViewConfigNavigateEvent navigateEvent)
{
//...
}
|
Furthermore, it's possible to change the navigation target.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
protected void onViewConfigNavigation(@Observes PreViewConfigNavigateEvent navigateEvent)
{
if(Wizard.Page1.class.equals(navigateEvent.getFromView()) &&
!Wizard.Page2.class.equals(navigateEvent.getToView()))
{
navigateEvent.navigateTo(DemoPages.HelloMyFacesCodi2.class);
}
}
|
Sometimes it's needed to add parameters. If you are using e.g. the implicit navigation feature of JSF 2.0+ you would just add them to the string you are using for the navigation. In case of type-safe view-configs it isn't possible to add such parameters directly. To add such parameters, it's required to use the @PageParameter
annotation. It's possible to annotate action-methods or view-config classes which represent a page with this annotation. So it's possible to enforce parameters for all JSF based navigations to a view or to add parameters just in case of special actions. Furthermore, it's possible to add multiple parameters with @PageParameter.List
. The usage for action methods is the same as the usage for view-configs. The following example shows a simple parameter.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Page
@PageParameter(key="customParam", value="#{customBean.value1}")
public class Index implements ViewConfig {}
|
In the previous section you have learned some details about the view-config mechanism provided by CODI. You can use these meta-classes for the navigation.
Example:
...
public Class<? extends ViewConfig> navigateToHomeScreen()
{
return Home.class;
}
Note | ||
---|---|---|
| ||
Some EL implementations like JUEL check the allowed return type explicitly. In combination with early implementations of Facelets you might see an exception which tells that action methods have to return strings. In such a case you can use |
Info | ||
---|---|---|
| ||
The new API of JSF 2 |
In case of type-safe navigation it's possible to observe navigations which have a view-config for the source-view as well as the target-view.
...
protected void onViewConfigNavigation(@Observes PreViewConfigNavigateEvent navigateEvent)
{
//...
}
Furthermore, it's possible to change the navigation target.
Code Block | |
---|---|
java | java |
title | Observe type-safe navigation event |
protected void onViewConfigNavigation(@Observes PreViewConfigNavigateEvent navigateEvent)
{
if(Wizard.Page1.class.equals(navigateEvent.getFromView()) &&
!Wizard.Page2.class.equals(navigateEvent.getToView()))
{
navigateEvent.navigateTo(DemoPages.HelloMyFacesCodi2.class);
}
}
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Page @Secured(OrderVoter.class) public final class OrderWizard implements ViewConfig { } |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@ApplicationScoped public class OrderVoter extends AbstractAccessDecisionVoter { @Inject private UserService userService; @Inject private User currentUser; public void checkPermission(InvocationContext invocationContext, Set<SecurityViolation> violations) { if(!this.loginServiceuserService.isActiveUser(this.currentUser)) { violations.add(newSecurityViolation("{inactive_user_violation}")); } } } |
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@ApplicationScoped public class OrderVoter extends AbstractAccessDecisionVoter { @Inject private UserService userService; @Inject private User currentUser; @Inject @Custom private MessageContext messageContext; public void checkPermission(InvocationContext invocationContext, Set<SecurityViolation> violations) checkPermission(InvocationContext invocationContext, Set<SecurityViolation> violations) { if(!this.userService.isActiveUser(this.currentUser)) { String reason = this.messageContext.message().text("{inactive_user_violation}").toText(); { if(!this.loginService.isActiveUser(this.currentUser))violations.add(newSecurityViolation(reason)); {} String reason = this.messageContext.message().text("{inactive_user_violation}").toText(); violations.add(newSecurityViolation(reason)); } } } } } |
Per default the created violation message gets added as faces-message. However, sometimes users don't have to see such messages (e.g. in case of autom. navigation to a login page). For such cases it's possible to introduce a custom SecurityViolationHandler
. As soon as a project contains a bean which implements this interface, the bean will be used for handling the violations (instead of adding them autom. as faces-message).
The following example shows how to create a default error page. It's just allowed to provide one default error page per application.
Instead of implementing ViewConfig
it's required to implement the DefaultErrorView
interface.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Page public final class Login implementsextends DefaultErrorView { } |
Note | ||
---|---|---|
| ||
|
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
//... public final class LoginPage implements Serializable { @PostConstruct { @PostConstruct protected void initBean() { //... } @InitView protected void initView() { //... } @PrePageAction protected void prePageAction() { //... } @PreRenderView protected void initBeanpreRenderView() { //... } @InitView protected void initView() { //... } @PrePageAction protected void prePageAction() { //... } @PreRenderView protected void preRenderView() { //... } } |
@PostConstruct
in a view-controller bean this lifecycle-callback is invoked before the rendering phase at latest. However, it's just called at the first time the instance of the bean is used (as view-controller bean - depending on the scope of the bean). If it's required to process logic every time before the page gets rendered, it's possible to use @PreRenderView
.
@InitView
is invoked after a view was created.
/...
}
}
|
@PostConstruct
in a view-controller bean this lifecycle-callback is invoked before the rendering phase at latest. However, it's just called at the first time the instance of the bean is used (as view-controller bean - depending on the scope of the bean). If it's required to process logic every time before the page gets rendered, it's possible to use @PreRenderView
.
@InitView
is invoked after a view was created.
Example:
Code Block |
---|
viewA created -> @InitView callback for viewA called
viewB created -> @InitView callback for viewB called
viewB created -> logic already initilized -> no callback
viewA created -> @InitView callback for viewA called
viewB created -> @InitView callback for viewB called
|
-> Such methods get called after the corresponding view was created.
The evaluation happens after every request-lifecycle-phase to avoid that other features or frameworks lead to unexpected calls if they have to create views temporarily (e.g. security frameworks).
@PrePageAction
is invoked directly before the action-method. In comparison to @BeforePhase(INVOKE_APPLICATION)
, @PrePageAction
also works for immediate actions. If you have to use a bean like the RequestTypeResolver
just inject it into the bean and use it.
@PreRenderView
is invoked before the page gets rendered. It allows e.g. to load data and change the target-view in case of an unexpected error.
@PostRenderView
is invoked after the rendering process and allows to do special clean-up.
Note | ||
---|---|---|
| ||
For normal pre-render view logic you can use phase-listener methods in combination with |
...