You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 27 Next »

Intro

The Intro page provides an overview 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!

Dependencies

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.

Scopes

Conversation Scope

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.

A CODI conversation scoped bean
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.ConversationGroup;

@ConversationScoped
public class DemoBean1 implements Serializable
{
  //...
}

... leads to a conversation which contains just one bean with the group DemoBean1.

That's it!

As soon as you access the bean, the conversation gets started!

Hint

If you would like to use the bean within your JSF pages, you have to add @Named (javax.inject.Named ).

Further details

Currently you will find also some further information here. We are going to merge both pages.

Grouped Conversations

(In case of CDI std. conversations there is just one big conversation which contains all conversation scoped beans.)
The grouped conversations provided by CODI are completely different. By default every conversation scoped bean exists in an "isolated" conversation. That means there are several parallel conversations within the same window.

Example:

Separated CODI conversations
@ConversationScoped
public class DemoBean2 implements Serializable
{
  //...
}

@ConversationScoped
public class DemoBean3 implements Serializable
{
  //...
}

... leads to two independent conversations in the same window (context).
If you close the conversation of DemoBean2, the conversation of DemoBean3 is still active.

If you have an use-case (e.g. a wizard) which uses multiple beans which are linked together very tightly, you can create a type-safe conversation group.

Grouped conversation scoped beans
interface Wizard1 {}

@ConversationScoped
@ConversationGroup(Wizard1.class)
public class DemoBean4 implements Serializable
{
  //...
}

@ConversationScoped
@ConversationGroup(Wizard1.class)
public class DemoBean5 implements Serializable
{
  //...
}

You can use @ConversationGroup to tell CODI that there is a logical group of beans. Technically @ConversationGroup is just a CDI qualifier. Internally CODI uses this information to identify a conversation. In the previous example both beans exist in the same conversation (group). If you terminate the conversation group, both beans will be destroyed. If you don't use @ConversationGroup explicitly, CODI uses the class of the bean as conversation group.

Injecting a conversation scoped bean with an explicit group
//...
public class CustomBean1
{
  @Inject
  @ConversationGroup(Group1.class)
  private CustomBean2 demoBean;

  @Inject
  @ConversationGroup(Group2.class)
  private CustomBean2 demoBean;
}

Since @ConversationGroup is a std. CDI qualifier you have to use it at the injection point. You have to do that esp. because it's possible to create beans of the same type which exist in different groups (e.g. via producer methods).

Example:

Producer methods which produce conversation scoped beans with different groups
interface Group1 {}
interface Group2 {}

public class CustomBean2
{
  @Produces
  @ConversationScoped
  @ConversationGroup(Group1.class)
  public CustomBean2 createInstanceForGroup1()
  {
    return new CustomBean2();
  }

  @Produces
  @ConversationScoped
  @ConversationGroup(Group2.class)
  public CustomBean2 createInstanceForGroup2()
  {
    return new CustomBean2();
  }
}

Terminating Conversations

You can inject the conversation via @Inject and use it to terminate the conversation immediately (see *) or you inject the current WindowContext which can be used to terminate a given conversation group.

Injecting and using the current conversation
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.ConversationScoped;
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.Conversation;

@ConversationScoped
public class DemoBean6 implements Serializable
{
  @Inject
  private Conversation conversation; //injects the conversation of DemoBean6 (!= conversation of DemoBean7)

  //...

  public void finish()
  {
    this.conversation.close();
  }
}

@ConversationScoped
public class DemoBean7 implements Serializable
{
  @Inject
  private Conversation conversation; //injects the conversation of DemoBean7 (!= conversation of DemoBean6)

  //...

  public void finish()
  {
    this.conversation.close();
  }
}
Injecting and using the explicitly grouped conversation
interface Wizard2 {}

@ConversationScoped
@ConversationGroup(Wizard2.class)
public class DemoBean8 implements Serializable
{
  @Inject
  private Conversation conversation; //injects the conversation of Wizard2 (contains DemoBean8 and DemoBean9)

  //...

  public void finish()
  {
    this.conversation.close();
  }
}

@ConversationScoped
@ConversationGroup(Wizard2.class)
public class DemoBean9 implements Serializable
{
  @Inject
  private Conversation conversation; //injects the conversation of Wizard2 (contains DemoBean8 and DemoBean9)

  //...

  public void finish()
  {
    this.conversation.close();
  }
}
Terminating a grouped conversation outside of the conversation
//...
public class DemoBean10 implements Serializable
{
  @Inject
  private WindowContext windowContext; //injects the whole window context (of the current window)

  //...

  public void finish()
  {
    this.windowContext.closeConversationGroup(Wizard2.class);  //closes the conversation of group Wizard2.class
  }
}
Alternative approach for terminating a conversation group
//...
public class DemoBean10 implements Serializable
{
  //...

  @CloseConversationGroup(group = Wizard2.class)
  public void finish()
  {
    //...
  }
}
Alternative approach for terminating a conversation group in case of an exception
//...
public class DemoBean10 implements Serializable
{
  //...

  @CloseConversationGroup(group = Wizard2.class, on = MyRuntimeException.class)
  public void finish()
  {
    //...
  }
}

These two alternative approaches can be used for simple use-cases.

Terminate all conversations
//...
public class DemoBean11 implements Serializable
{
  @Inject
  private WindowContext windowContext;

  //...

  public void finish()
  {
    this.windowContext.closeConversations();  //closes all existing conversations within the current window (context)
  }
}

Hint

Since the View-Access scope is just a different kind of a conversation #closeConversations also terminates all view-access scoped beans.
There will be a SPI to customize this behavior. Usually you will need #closeConversations e.g. if the user triggers a navigation via the main-menu and in such a case you usually exit the current use-case. So it makes sense that all kinds of conversations will be closed.

Restarting 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 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).

Restarting a conversation
@ConversationScoped
public class DemoBean12 implements Serializable
{
  @Inject
  private Conversation conversation;

  //...

  public void finish()
  {
    this.conversation.restart();
  }
}

Hint

Compared to std. CDI conversations CODI provides completely different conversation concepts. "Just the name is the same."
So please don't try to use the same implementation patterns which you might have learned for std. conversations.
CDI conversations are comparable to MyFaces Orchestra conversations.

View Access Scope

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.

Access scoped bean
@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.

Window Scope

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.

Window scoped bean
@WindowScoped
public class PreferencesBean implements Serializable
{
  //...
}

Terminating the Window Scope/ window scoped Beans

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.

Terminate the whole content of the window
//...
public class CustomWindowControllerBean1
{
  @Inject
  private WindowContext windowContext;

  //...

  public void closeWindow()
  {
    this.windowContext.close();
  }
}
Terminate all window scoped beans
//...
public class CustomWindowControllerBean2
{
  @Inject
  private WindowContext windowContext;

  //...

  public void finish()
  {
    this.windowContext.closeConversationGroup(WindowScoped.class);
  }
}

JSF 2.0 Scope Annotations

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.

Flash Scope

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.

Dependency Injection

CODI allows using @Inject within the following JSF (1.2 and 2.0) artifacts:

  • Converter
  • Validator
  • PhaseListener

As soon as a converter or a validator is annotated with @Advanced it's possible to use @Inject.

Example for a validator:

@Inject within a JSF validator
@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:

@Inject within a JSF converter
@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;
  }

  //...
}

Life-cycle Annotations

Phase-Listener Methods

As an alternative to a full phase-listener CODI allows to use observers as phase-listener methods.

Example:

Global observer method for phase-events
protected void observePreRenderView(@Observes @BeforePhase(PhaseId.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.

Observer method for phase-events for a specific view
@View(DemoPage.class)
public void observePostInvokeApplication(@Observes @AfterPhase(PhaseId.INVOKE_APPLICATION) PhaseEvent event)
{
  //...
}

For further details about DemoPage.class please have a look at the view-config section.

Hint

If you don't need the PhaseEvent as parameter, you can just annotate your methods with @BeforePhase(...) and @AfterPhase(...).

Hint

@View is an interceptor. The disadvantage is that intercepted beans introduce an overhead.
If you would like to use this mechanism for implementing pre-render view logic, you should think about using the @PageBean annotation.
Further details are available in the view-config section.

Phase-Listener

CODI provides an annotation for phase-listeners:

PhaseListener configured via annotation
@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:

@Inject within a JSF phase-listener
@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;
  }
}

Why @JsfPhaseListener instead of @PhaseListener?

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.

Faces-Request-Listener

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.

Example:

Observer method with @BeforeFacesRequest
protected void initFacesRequest(@Observes @BeforeFacesRequest FacesContext facesContext)
{
  //...
}

Producers

CODI offers a bunch of producers for JSF artifacts.

Example:

Injection of the current FacesContext
@Inject
private FacesContext facesContext;

JsfLifecyclePhaseInformation

Helper for detecting the current phase of the JSF request lifecycle.

Example:

Detecting the current phase
public class MyBean
{
    @Inject
    private JsfLifecyclePhaseInformation phaseInformation;

    public void execute()
    {
        if (this.phaseInformation.isProcessValidationsPhase() || this.phaseInformation.isUpdateModelValuesPhase())
        {
            //...
        }
    }
}

Type-safe View Config

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-config.

View config for /home.xhtml
@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.

If you would like to group pages (you will learn some reasons for that later on), you can nest the classes.

Grouping pages
public abstract class Wizard implements ViewConfig
{
    @Page
    public final class Page1 extends Wizard
    {
    }

    @Page
    public final class Page2 extends Wizard
    {
    }
}

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").

Hint

At @Page(basePath = "") we have to override the default with an empty string to have a virtual view config which doesn't affect the final name.

Organizing your pages

View configs allow to reflect your folder structure in the meta-classes.

Grouping pages and folder structures
public abstract class Wizard implements ViewConfig
{
    @Page
    public final class Step1 extends Wizard
    {
    }

    @Page
    public final class Step2 extends 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.

Hint

While the nested classes lead to the final path of the file, the inheritance allows to configure a bunch of pages.

Grouping pages and folder structures
@Page(navigation = REDIRECT)
public abstract class Wizard implements ViewConfig
{
    @Page
    public final class Step1 extends Wizard
    {
    }

    @Page
    public final class Step2 extends 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}.

Hint

Besides this naming convention you can use basePath to force a different name. Furthermore, @Page allows to adjust the navigation mode (forward (= default) vs. redirct) and the file extension (default: xhtml) as well as the name of the page itself.

Custom view meta-data

It's possible to use custom annotations in view-config classes. Just annotate the custom annotation with @ViewMetaData.

Implementation of a custom view meta-data annotation
@Target({TYPE})
@Retention(RUNTIME)
@Documented

@ViewMetaData
public @interface ViewMode
{
    //...
}
Usage of a custom view meta-data annotation
@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.

Implementation of a custom view meta-data annotation which allows to override meta-data
@Target({TYPE})
@Retention(RUNTIME)
@Documented

@ViewMetaData(override = true)
public @interface PageIcon
{
    //...
}
Override view meta-data annotation
@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
    {
    }
}

Type-safe Navigation

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:

Action method with type-safe navigation
public Class<? extends ViewConfig> navigateToHomeScreen()
{
    return Home.class;
}

Hint

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 Home.class.getName().

PreViewConfigNavigateEvent

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.

Observe type-safe navigation event
protected void onViewConfigNavigation(@Observes PreViewConfigNavigateEvent navigateEvent)
{
    //...
}

Furthermore, it's possible to change the navigation target.

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);
    }
}

Type-safe Security

Action method with type-safe navigation
@Page
@Secured(OrderVoter.class)
public final class OrderWizard implements ViewConfig
{
} 
Example for an AccessDecisionVoter
@ApplicationScoped
public class OrderVoter extends AbstractAccessDecisionVoter
{
    @Inject
    private UserService userService;

    @Inject
    private User currentUser;

    public void checkPermission(InvocationContext invocationContext, Set<SecurityViolation> violations)
    {
        if(!this.loginService.isActiveUser(this.currentUser))
        {
            violations.add(newSecurityViolation("{inactive_user_violation}"));
        }
    }
}

The message-key of the previous example will be passed to the MessageContext with the Jsf qualifier. You can also use a hardcoded inline message. If you would like to use a different MessageContext you can just inject it (see the following example).

Example for an AccessDecisionVoter with a custom MessageContext
@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)
    {
        if(!this.loginService.isActiveUser(this.currentUser))
        {
            String reason = this.messageContext.message().text("{inactive_user_violation}").toText();
            violations.add(newSecurityViolation(reason));
        }
    }
}

(Security) Error pages

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.

Default error page
@Page
public final class Login implements DefaultErrorView
{
}

Hint

@Secured allows to override the default error page for a specific page or a group of pages.

If there isn't an error page, CODI will throw an AccessDeniedException.

Page-Beans

It's a common pattern in JSF applications to create beans which act as page-controllers (aka Page-Beans). Such beans are mapped to 1-n views.
Usually there is just one concrete implementation.

CODI allows to specify the page-bean as optional meta-data via the view-config mechanism.
You can use this approach to load the page-bean before the rendering process starts. So the post-construct method (= methods annotated with @PostConstruct) will be invoked if it is needed. Furthermore, it's possible to use @BeforePhase(...) and @AfterPhase(...) without observer syntax of CDI.

View-config with Page-Bean

@Page
@PageBean(LoginPage.class)
public final class Login implements ViewConfig
{
}
Page-Bean
//...
public final class LoginPage implements Serializable
{
  @PostConstruct
  protected void initBean()
  {
    //...
  }

  @AfterPhase(INVOKE_APPLICATION)
  protected void postPageAction()
  {
    //...
  }

  @BeforePhase(RENDER_RESPONSE)
  protected void preRenderView()
  {
    //...
  }
}

... you can use @BeforePhase and @AfterPhase in the same way like the Phase-Listener Methods described above (just without the need of the PhaseEvent).

Page-Bean with view-controller annotations
//...
public final class LoginPage implements Serializable
{
  @PostConstruct
  protected void initBean()
  {
    //...
  }

  @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.

@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.

Hint

For normal pre-render view logic you can use phase-listener methods in combination with @View.
Attention: the performance depends on the interceptor-performance of your CDI implementation.

Sometimes it's required to use multiple page controllers for a page (e.g. in case of two very different parts in the page which should be handled by different view-controllers). Such a use-case isn't very common, however, via @PageBean.List it's possible to attach multiple pages-beans to a view-config.

View-config with multiple Page-Beans

@Page
@PageBean.List({
  @PageBean(Bean1.class),
  @PageBean(Bean2.class)
})
public final class UseCase1 implements ViewConfig
{
}
  • No labels