Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

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.

Code Block
java
java
titleA CODI conversation scoped beanjava
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.ConversationGroup;

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

...

(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:

Code Block
java
java
titleSeparated CODI conversationsjava
@ConversationScoped
public class DemoBean2 implements Serializable
{
  //...
}

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

...

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.

Code Block
java
java
titleGrouped conversation scoped beansjava
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.

Code Block
java
java
titleInjecting a conversation scoped bean with an explicit groupjava
//...
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:

Code Block
java
java
titleProducer methods which produce conversation scoped beans with different groupsjava
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();
  }
}

...

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.

Code Block
java
java
titleInjecting and using the current conversationjava
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();
  }
}
Code Block
java
java
titleInjecting and using the explicitly grouped conversationjava
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();
  }
}
Code Block
java
java
titleTerminating a grouped conversation outside of the conversationjava
//...
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
  }
}
Code Block
java
java
titleAlternative approach for terminating a conversation groupjava
//...
public class DemoBean10 implements Serializable
{
  //...

  @CloseConversationGroup(group = Wizard2.class)
  public void finish()
  {
    //...
  }
}
Code Block
java
java
titleAlternative approach for terminating a conversation group in case of an exceptionjava
//...
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.

Code Block
java
java
titleTerminate all conversationsjava
//...
public class DemoBean11 implements Serializable
{
  @Inject
  private WindowContext windowContext;

  //...

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

...

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

Code Block
java
java
titleRestarting a conversationjava
@ConversationScoped
public class DemoBean12 implements Serializable
{
  @Inject
  private Conversation conversation;

  //...

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

...

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
java
java
titleExplicitly listing beans of a sub-groupjava
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
java
java
titleTerminating a sub-groupjava

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:

Code Block
java
java
titleImplicit sub-groupjava
public interface 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);

...

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
java
java
titleWindow scoped beanjava
@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
java
java
titleTerminate the whole content of the windowjava
//...
public class CustomWindowControllerBean1
{
  @Inject
  private WindowContext windowContext;

  //...

  public void closeWindow()
  {
    this.windowContext.close();
  }
}
Code Block
java
java
titleTerminate all window scoped beansjava
//...
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
java
java
titleAccess scoped beanjava
@ViewAccessScoped
public class WizardBean implements Serializable
{
  //...
}

...

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
java
java
titleRestScoped beanjava
@RestScoped
public class CarBean implements Serializable
{
  //...
}

...

With JSF2 you can use GET-Requests with h:link . It's recommended to use it instead of a plain HTML link.

Code Block
xml
xml
titleJSF2 component for GET-Requestsxml
<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
xml
xml
titleHTML link as an alternative to the JSF2 component for GET-Requestsxml
<a href="#{facesContext.externalContext.request.contextPath}/myPage.xhtml?windowId=#{currentWindow.id}">My Page< /a >

...

Example for a validator:

Code Block
java
java
title@Inject within a JSF validatorjava
@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
java
java
title@Inject within a JSF converterjava
@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
java
java
titleGlobal observer method for phase-eventsjava
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
java
java
titleObserver method for phase-events for a specific viewjava
@View(DemoPage.class)
public void observePostInvokeApplication(@Observes @AfterPhase(JsfPhaseId.INVOKE_APPLICATION) PhaseEvent event)
{
  //...
}

...

CODI provides an annotation for phase-listeners:

Code Block
java
java
titlePhaseListener configured via annotationjava
@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
java
java
title@Inject within a JSF phase-listenerjava
@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;
  }
}

...

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:

Code Block
java
java
titleObserver method with @BeforeFacesRequestjava
protected void initFacesRequest(@Observes @BeforeFacesRequest FacesContext facesContext)
{
  //...
}

...

CODI offers a bunch of producers for JSF artifacts.

Example:

Code Block
java
java
titleInjection of the current FacesContextjava
@Inject
private FacesContext facesContext;

...

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

Example:

Code Block
java
java
titleDetecting the current phasejava
public class MyBean
{
    @Inject
    private JsfLifecyclePhaseInformation phaseInformation;

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

...

The following example shows a simple view-config.

Code Block
java
java
titleView config for /home.xhtmljava
@Page
public final class Home implements ViewConfig
{
}

...

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

Code Block
java
java
titleGrouping pagesjava
public interface Wizard extends ViewConfig
{
    @Page
    public final class Page1 implements Wizard
    {
    }

    @Page
    public final class Page2 implements Wizard
    {
    }
}

...

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

Code Blockcode
java
java
titleGrouping pages and folder structuresjava
public interface Wizard extends ViewConfig
{
    @Page
    public final class Step1 implements Wizard
    {
    }

    @Page
    public final class Step2 implements Wizard
    {
    }
}

...

Note
titleHint

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

Code Block
java
java
titleGrouping pages and folder structuresjava
@Page(navigation = REDIRECT)
public interface Wizard extends ViewConfig
{
    @Page
    public final class Step1 implements Wizard
    {
    }

    @Page
    public final class Step2 implements Wizard
    {
    }
}

...

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

Code Block
java
java
titleImplementation of a custom view meta-data annotationjava
@Target({TYPE})
@Retention(RUNTIME)
@Documented

@ViewMetaData
public @interface ViewMode
{
    //...
}
Code Block
java
java
titleUsage of a custom view meta-data annotationjava
@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.

Code Block
java
java
titleImplementation of a custom view meta-data annotation which allows to override meta-datajava
@Target({TYPE})
@Retention(RUNTIME)
@Documented

@ViewMetaData(override = true)
public @interface PageIcon
{
    //...
}
Code Block
java
java
titleOverride view meta-data annotationjava
@PageIcon(...)
public interface UseCase1 extends ViewConfig
{
    @Page
    //inherits the page-icon of the use-case
    public final class Step1 implements Wizard
    {
    }

    @Page
    @PageIcon(...) //overrides the page-icon of the use-case
    public final class Step2 implements Wizard
    {
    }
}
Code Block
java
java
titleResolve view meta-data annotationsjava
@Inject
private ViewConfigResolver viewConfigResolver;

this.viewConfigResolver.getViewConfigDescriptor(UseCase1.Step2.class).getMetaData();

//or something like:
this.viewConfigResolver.getViewConfigDescriptor(this.facesContext.getViewRoot().getViewId()).getMetaData();

...

Type-safe view configs also allow to easily specify the valid page range.

Code Block
java
java
titleSpecified page rangejava
public interface PublicView extends ViewConfig {}

@Page
public class ReportPage implements PublicView {}

//...

@Secured(LoginAccessDecisionVoter.class)
public interface Internal extends ViewConfig
{
    @Page
    public class ReportPage implements Internal {}
}

//...

public Class<? extends Internal> showInternalReport()
{
    //...
    return Internal.ReportPage.class;
}

//...

public Class<? extends PublicView> showPublicReport()
{
    //...
    return ReportPage.class;
}

...

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
java
java
titleAction method with type-safe navigationjava
public Class<? extends ViewConfig> navigateToHomeScreen()
{
    return Home.class;
}

...

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
xml
xml
titleJSF-Navigation via GET requestsxml
<!-- 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
xml
xml
titleThe corresponding code in MyControllerxml
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
java
java
titleObserve type-safe navigation eventjava
protected void onViewConfigNavigation(@Observes PreViewConfigNavigateEvent navigateEvent)
{
    //...
}

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

Code Block
java
java
titleObserve type-safe navigation eventjava
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
java
java
titlejava
@Page
@PageParameter(key="customParam", value="#{customBean.value1}")
public class Index implements ViewConfig {}

Type-safe Security

Code Block
java
java
titleAction method with type-safe navigationjava
@Page
@Secured(OrderVoter.class)
public final class OrderWizard implements ViewConfig
{
} 
Code Block
java
java
titleExample for an AccessDecisionVoterjava
@ApplicationScoped
public class OrderVoter extends AbstractAccessDecisionVoter
{
    @Inject
    private UserService userService;

    @Inject
    private User currentUser;

    public void checkPermission(InvocationContext invocationContext, Set<SecurityViolation> violations)
    {
        if(!this.userService.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).

Code Block
java
java
titleExample for an AccessDecisionVoter with a custom MessageContextjava
@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.userService.isActiveUser(this.currentUser))
        {
            String reason = this.messageContext.message().text("{inactive_user_violation}").toText();
            violations.add(newSecurityViolation(reason));
        }
    }
}

...

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
java
java
titleDefault error pagejava
@Page
public final class Login extends DefaultErrorView
{
}

...

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.

Code Block
java
java
titleView-config with Page-Beanjava

@Page
@PageBean(LoginPage.class)
public final class Login implements ViewConfig
{
}
Code Block
java
java
titlePage-Beanjava
//...
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).

Code Block
java
java
titlePage-Bean with view-controller annotationsjava
//...
public final class LoginPage implements Serializable
{
  @PostConstruct
  protected void initBean()
  {
    //...
  }

  @InitView
  protected void initView()
  {
    //...
  }

  @PrePageAction
  protected void prePageAction()
  {
    //...
  }

  @PreRenderView
  protected void preRenderView()
  {
    //...
  }
}

...

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.

Code Block
java
java
titleView-config with multiple Page-Beansjava

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

...

the @PageBean example above would look like:

Code Block
java
java
titleView controller example without @PageBeanjava

@Page
public final class UseCase1 implements ViewConfig
{
}

@Model
@View(UseCase1.class)
public class Bean1 implements ViewConfig
{
    @PreRenderView
    protected void preRenderView()
    {
        //...
    } 
}


@Model
@View(UseCase1.class)
public class Bean2 implements ViewConfig
{
    @PreRenderView
    protected void preRenderView()
    {
        //...
    } 
}

...

If you have a dedicated root-folder for all your pages (and sub-folders) you can reflect it in your package structure. The following example shows a class marked with @InlineViewConfigRoot in the package *.pages. So the root folder has to have the name 'pages'. Every sub-package will be mapped to a sub-folder. In case of Inline-View-Configs the page-bean has to implement the ViewConfig in-/directly and has to be annotated with @Page. You can use the same features of the normal View-Config including type-safe navigation, lifecycle callback annotations,...

Code Block
java
java
titleInline-View-Config example 1java
package my.pkg.pages;

@InlineViewConfigRoot
public final class RootMarker
{
}

package my.pkg.pages.registration;
//...

@Named
@RequestScoped
@Page
public class RegistrationStep1 implements ViewConfig
{
  public Class<? extends ViewConfig> confirm()
  {
    //...
    return RegistrationStep2Page.class;
  }
}

//will be interpreted as /pages/registration/registrationStep1.xhtml 

...

Furthermore, it's possible to specify a so called pageBeanPostfix for allowing to use a name convention for your pages beans which won't be reflected by the xhtml file name.

Code Block
java
java
titleInline-View-Config example 2java
package my.pkg.pages;

@InlineViewConfigRoot(basePath = "/*", pageBeanPostfix = "Controller")
public final class RootMarker
{
}

package my.pkg.pages.registration;
//...

@Named
@RequestScoped
@Page
public class RegistrationStep3Controller implements ViewConfig
{
  //...
}

//will be interpreted as /registration/registrationStep3.xhtml

If you have a fine grained package structure which isn't reflected in the folder-structure of your pages (or a different name has to be used), it's possible to specify a basePath. Without a * at the end, all sub-packages are ignored.

Code Block
java
java
titleInline-View-Config example 3java
package my.pkg.pages;

@InlineViewConfigRoot(basePath = "/pages/", pageBeanPostfix = "Page")
public final class RootMarker
{
}

package my.pkg.pages.registration;
//...

@Named
@RequestScoped
@Page
public class RegistrationStep2Page implements ViewConfig
{
  //...
}

//will be interpreted as /pages/registrationStep2.xhtml

Compared to the previous example the next example shows a custom basePath and all sub-packages will be mapped to sub-folders.

Code Block
java
java
titleInline-View-Config example 4java
package my.pkg.pages;

@InlineViewConfigRoot(basePath = "/views/*")
public final class RootMarker
{
}

package my.pkg.pages.registration;
//...

@Named
@RequestScoped
@Page
public class RegistrationStep4 implements ViewConfig
{
  //...
}

//will be interpreted as /views/registration/registrationStep4.xhtml