JSF


Type-safe View-Configs

Intro

Type-safe view-configs are static configs which can be used in combination with every view-technology which is based on Java. Currently DeltaSpike itself provides an integration for JSF, however, the basic concepts are independent of it. (Since DeltaSpike provides the default integration only for JSF, the whole documentation for view-configs is located here.)

Thanks to features like multiple (meta-data-)inheritance via interfaces, it provides a powerful approach to bind meta-data to one or multiple views. In case of the JSF integration it's possible to provide e.g. type-safe meta-data for security, navigation, callbacks for view-controllers. Beyond configuring view (/pages) via this concept, it's also possible to use the (view-)config classes for type-safe navigation. Since it's std. Java, you can benefit from any Java-IDE and you don't need special IDE-Addons to use it efficiently.

Even the concepts provided by modules (of DeltaSpike itself) are based on the basic API provided by the Core. So it's possible to introduce custom concepts the same way DeltaSpike itself does.

Motivation

Instead of learning the concepts and rules of view-configs provided by DeltaSpike, it might be easier for simple demos to just type some simple(r) strings. So why should you use something which is slightly more work initially?

The short answer is:

It gives a good return in case of real applications (esp. beyond simple demos).

The long answer is:

You can benefit from it from the first second:

  • It's type-safe ->
    • the Java compiler ensures that you don't have typos at the final usages (and the rest can be checked during bootstrapping of the application)
    • you can benefit from the auto.complete features of any modern Java IDE.
  • If you change the name of a file/folder, you need only one (easy) code-change in a single place and your (std. Java-) IDE will do the rest for you (= update all usages) without a special plug-in
  • It's possible to restrict the navigation target -> you can ensure that the navigation target is still the intended one (e.g. after a refactoring)
  • You can configure meta-data in a central place (which can get inherited via multiple inheritance based on Java interfaces)
  • Easier for developers to find usages
  • Allows easy(er) refactorings and maintenance
  • You can use your IDE more efficiently esp. in large projects (there are some users who initially switched to it, because their tools for displaying the config they had before open large config files very slowly...)
  • Modern Java IDEs show inheritance of interfaces and classes in a nice way. Since the view-config is based on std. classes and interfaces, you can benefit from it easily.

Advantages which are planned for later (= currently not supported):

  • It's possible to check if the configured folders and files really exist during/after the bootstrapping phase of the application (currently it isn't implemented, but it's possible to do it).
  • It's also easy(er) for tools (IDE plugins,...) to validate it

If you are still not convinced, you just have to try it. You will see how your daily workflow benefits from it pretty soon.

Basic API usages

While reading this section keep the following simple rules in mind:

  • Meta-data gets inherited along the path of Java inheritance
  • File-/Folder- paths are build based on nesting classes and interfaces

Usually users don't need to be aware of all descriptors, SPIs,... which are described by this documentation.

There are a lot of possibilities to configure views and some of them are optional. The following examples show some of them in combination with features provided by the JSF- and Security-Module of DeltaSpike.

The following example shows the minimal syntax for providing a config for a view (/page).

public class MyPage implements ViewConfig
{
}

Since it's a class (and not an interface) it's autom. recognized as config for a page (and not a folder) and the default settings get applied during bootstrapping. In case of JSF you can use it for navigation e.g. via action-methods.

public Class<? extends ViewConfig> toNextPage()
{
    return MyPage.class;
}

This leads to a forward to /myPage.xhtml. Information like base-path, file- (and folder-)name/s, file-extension, navigation mode, view-params,... can be customized with the corresponding (meta-data-)annotations. One of those annotations provided by the JSF module (which is optional) is @View. That means the following example leads to the same as the first one.

@View //optional
public class MyPage implements ViewConfig
{
}

But it's also possible to reflect the folder structure via nesting of interfaces and classes. An example for it is:

public interface Pages
{
    class Index implements ViewConfig { }

    interface AdminArea extends ViewConfig
    {
        class Index implements Admin { }
    }
}

In case of the JSF integration it leads to the following view-ids:
/pages/index.xhtml
/pages/adminArea/index.xhtml

Like the optional @View for pages represented by the classes, it's possible to use the optional @Folder annotation for directories represented by the (nested) interfaces.

Furthermore, it's possible to inherit meta-data along with the normal inheritance.

In the following example Pages.Admin.Index, Pages.Admin.Home and Pages.Admin.Statistics.Home inherit the meta-data from Pages.Admin because they implement the interface whereas Pages.Admin.Statistics.Index doesn't. However, Pages.Admin.Home overrides View#navigation. During the bootstrapping process the meta-data gets merged and at runtime you only see the final result (which is cached).

public interface Pages
{
    @View(name = "home", extension = JSP)
    class Index implements ViewConfig { }

    @View(navigation = REDIRECT, viewParams = INCLUDE)
    interface Admin extends ViewConfig
    {
        interface Statistics
        {
            @View //optional
            class Index implements ViewConfig { }

            class Home implements Admin { }
        }

        class Index implements Admin { }

        @View(navigation = FORWARD)
        class Home implements Admin { }
    }
}

In this case Pages.Admin.Statistics is just an interface to reflect the folder structure. For sure it's also possible that it extends an existing view-config interface and other folders and/or pages inherit its meta-data (like Pages.Admin).

Furthermore, inheritance can be used to ensure navigation to the correct area in the application. In the following example the return type of the action-method (and therefore the compiler of Java) ensures that the navigation target of this method is within the admin-area.

public Class<? extends Pages.Admin> toNextPage()
{
    return Pages.Admin.Index.class;
}

File (@View) and Folder (@Folder) paths

@View as well as @Folder are optional annotations. @Folder is only needed for using a different folder-name or for marking folder configs if they don't inherit from org.apache.deltaspike.core.api.config.view.ViewConfig nor have a view-config for a page nested into them (like Pages.Wizard1.Step1). If it isn't used explicitly, it gets added automatically (so you can query the meta-data at runtime even in cases you haven't placed the annotations explicitly). @View allows to customize a bit more and it also gets added automatically if it isn't used explicitly. Whereas @Folder gets added to all nested interfaces (above a view-config class - like Pages and Pages.Wizard1), @View only gets added to classes which in-/directly inherit from org.apache.deltaspike.core.api.config.view.ViewConfig (like Pages.Wizard1.Step1).

That means at runtime the following two configs lead to the same.

public interface Pages
{
    interface Wizard1
    {
        class Step1 implements ViewConfig { }
    }
}

//leads to the same as

@Folder
public interface Pages
{
    @Folder
    interface Wizard1
    {
        @View
        class Step1 implements ViewConfig { }
    }
}

The example above leads to the following paths:

  • /pages/
  • /pages/wizard1
  • /pages/wizard1/step1.xhtml

To customize it you can use @Folder#name, @View#basePath, @View#name and @View#extension (or you register custom NameBuilders inline or globally).

@Folder#name

The rules are pretty simple. You will get what you write. There are only two additional features:

  • You don't have to care about duplicated '/' (e.g. /folder1//folder2/step1.xhtml would get corrected auto. to /folder1/folder2/step1.xhtml)
  • With "." at the beginning (e.g. "./") you can keep the path before.

The following example

interface Pages
{
    @Folder(name = "/w1/")
    interface Wizard1
    {
        class Step1 implements ViewConfig { }
    }

    @Folder(name = "./w2/")
    interface Wizard2 extends ViewConfig
    {
        class Step1 implements Wizard2 { }   //ViewConfig is inherited indirectly
    }
}

leads to the following paths:

  • /pages/
  • /w1/
  • /w1/step1.xhtml
  • /pages/w2/step1.xhtml

@View

The same naming rules apply to @View#basePath. However, it's only valid to be used at view-config nodes which represent pages (-> classes and not interfaces). On interfaces always use @Folder (@View#basePath will get ignored there).

interface Pages
{
    interface Wizard1
    {
        @View //optional
        class Step1 implements ViewConfig { }

        @View(basePath = "/")
        class Step2 implements ViewConfig { }

        @View(basePath = "./") //or just "."
        class Step3 implements ViewConfig { }

        @View(basePath = "/w1/")
        class Step4 implements ViewConfig { }

        @View(basePath = "./w1/")
        class Step5 implements ViewConfig { }
    }
}

leads to the following paths:

  • /pages
  • /pages/wizard1/
  • /pages/wizard1/step1.xhtml
  • /step2.xhtml
  • /pages/wizard1/step3.xhtml
  • /w1/step4.xhtml
  • /pages/wizard/w1/step5.xhtml

and depending on additional meta-data you would like to inherit (e.g. @View(navigation = REDIRECT)), you can also use:

@View(navigation = REDIRECT)
interface Pages extends ViewConfig 
{
    interface Wizard1 extends Pages
    {
        @View
        class Step1 implements Wizard1 { }

        @View(basePath = "/")
        class Step2 implements Wizard1 { }

        @View(basePath = "./")
        class Step3 implements Wizard1 { }

        @View(basePath = "/w1/")
        class Step4 implements Wizard1 { }

        @View(basePath = "./w1/")
        class Step5 implements Wizard1 { }
    }
}

It leads to the same paths, but in addition @View#navigation gets inherited along the inheritance path.

Since the view-config is static, an approach to add parameters is needed. The following part shows different possibilities to add parameters which end up in the final URL after '?' (in case of the integration with JSF). It isn't needed to add all (types of) parameters that way. Some get added autom. based on special meta-data (e.g. @View#navigation and @View#viewParams). Instead of adding "faces-redirect=true" manually it's done for you as soon as you are using @View(navigation = REDIRECT). The same goes for "includeViewParams=true" and @View(viewParams = INCLUDE).

Static Configuration via @NavigationParameter

In some cases it's needed to add an information in any case. So you can annotate the view-config class with @NavigationParameter. Supported values are static strings or EL-expressions.

public interface Pages extends ViewConfig
{
    @NavigationParameter(key = "param1", value = "staticValue1")
    class Index implements Pages { }

    @NavigationParameter.List({
        @NavigationParameter(key = "param1", value = "staticValue1"),
        @NavigationParameter(key = "param2", value = "#{myBean.property1}")
    })
    class Overview implements Pages { }
}

Instead of using parameters in any case, it's also possible to configure them statically for particular methods:

@Model
public class PageBean
{
    @NavigationParameter(key = "param2", value = "#{myBean.property1}")
    public Class<? extends ViewConfig> actionMethod1()
    {
        return SimplePageConfig.class;
    }

    @NavigationParameter.List({
        @NavigationParameter(key = "param1", value = "staticValue1"),
        @NavigationParameter(key = "param2", value = "staticValue2")
    })
    public Class<? extends ViewConfig> actionMethod2()
    {
        return SimplePageConfig.class;
    }
}

Dynamic Configuration via NavigationParameterContext

Instead of using parameters in a static fashion (as shown above), it's also possible to add them dynamically (e.g. in case of special conditions).

@Named
@SessionScoped
public class PageBean
{
    private int currentValue = -10;

    @Inject
    private NavigationParameterContext navigationParameterContext;

    public Class<? extends ViewConfig> actionMethod()
    {
        currentValue++;

        if (currentValue >= 0)
        {
            this.navigationParameterContext.addPageParameter("cv", this.currentValue);
        }
        return SimplePageConfig.class;
    }
}

Security Integration via @Secured

This annotation is a custom view-meta-data provided by the Security-module which allows to integrate 3rd party frameworks (or custom approaches) to secure pages as well as whole folders. You can annotate specific parts or a marker-interface. CustomAccessDecisionVoter used in the following example can be any implementation of org.apache.deltaspike.security.api.authorization.AccessDecisionVoter and needs to be a std. CDI bean which means you can use dependecy-injection to trigger any kind of security check. All parts which inherit from SecuredPages (Pages.Admin, Pages.Admin.Index and Pages.Admin.Home) are protected by CustomAccessDecisionVoter.

(It's easy to check this hierarchy in a modern Java-IDE. Only for displaying the final meta-data for every node in the IDE a special plug-in would be needed.)

@Secured(CustomAccessDecisionVoter.class)
public interface SecuredPages {}

@View(navigation = REDIRECT)
public interface Pages extends ViewConfig
{
    class Index implements Pages { }

    interface Admin extends Pages, SecuredPages
    {
        class Index implements Admin { }

        @View(navigation = FORWARD)
        class Home implements Admin { }
    }
}

For sure it's also possible to use it without a special interface. In this case you would need:

@View(navigation = REDIRECT)
public interface Pages extends ViewConfig
{
    class Index implements Pages { }

    @Secured(CustomAccessDecisionVoter.class)
    interface Admin extends Pages
    {
        class Index implements Admin { }

        @View(navigation = FORWARD)
        class Home implements Admin { }
    }
}

or

@View(navigation = REDIRECT)
public interface Pages extends ViewConfig
{
    class Index implements Pages { }

    interface Admin extends Pages
    {
        @Secured(CustomAccessDecisionVoter.class)
        class Index implements Admin { }

        @Secured(CustomAccessDecisionVoter.class)
        @View(navigation = FORWARD)
        class Home implements Admin { }
    }
}

View-Controller Callbacks via @ViewControllerRef

This annotation is a custom view-meta-data provided by the JSF-module which allows to configure beans which should act as view-controllers. That means they can use view-controller callbacks like @InitView, @PreViewAction, @PreRenderView and @PostRenderView. The following example shows the usage of @PreRenderView.

//@View //optional
@ViewControllerRef(MyPageController.class)
public class MyPage implements ViewConfig
{
}

@Model
public class MyPageController
{
    @PreRenderView
    protected void load()
    {
        //...
    }
}

Referencing Views via @ViewRef

With @ViewControllerRef#value you can annotate a view-config class to bind (/reference) a controller to it. @ViewRef#config allows the same in the other direction. Use an existing view-config to reference one or many view/s.

That means e.g.

public interface Pages extends ViewConfig
{
    class Index implements Pages { }
}

@ViewRef(Pages.Index.class)
//...
public class IndexController implements Serializable
{
    @PreRenderView
    protected void preRenderView()
    {
        //...
    }

    //...
}

leads to the invocation of the pre-render-view logic before /pages/page1.xhtml gets rendered (and it won't be called for other pages).

Using the (optional) ViewNavigationHandler

With JSF you typically navigate with the action-method bound to a command-component. However, also JSF supports manual navigation via javax.faces.application.NavigationHandler. With ViewNavigationHandler DeltaSpike provides an equivalent optimized for type-safe view-configs which is easier to use (and can be used also for other (supported) view technology).

A simple example is:

public interface Pages {
    class Index implements ViewConfig { }
}

@Model
public class AnyController
{
    @Inject
    private ViewNavigationHandler viewNavigationHandler;

    public void anyMethod()
    {
        //navigates to /pages/index.xhtml
        this.viewNavigationHandler.navigateTo(Pages.Index.class);
    }
}

Also in this case (optional) meta-data will be used for the navigation process, since ViewNavigationHandler just delegates to the active navigation-handler (of JSF).

Configuring a Default Error-View

It's possible to mark one view-config class as default error-view. That means in case of errors it will be used as navigation target automatically. Furthermore, it's also possible to use it in your code instead of hardcoding your error-view across the whole application.

In case of

public interface Pages {
    class Index implements ViewConfig { }

    class CustomErrorPage extends DefaultErrorView { }
}

it's possible to navigate with DefaultErrorView.class instead of hardcoding it to Pages.CustomErrorPage.class.

@Model
public class PageController
{
    public Class<? extends ViewConfig> actionWithoutError()
    {
        return Pages.Index.class;
    }

    public Class<? extends ViewConfig> actionWithError()
    {
        //navigates to the view which is configured as default error-view
        return DefaultErrorView.class;
    }
}

If you are outside of an action-method you can also use it in combination with ViewNavigationHandler.

@Model
public class AnyController
{
    @Inject
    private ViewNavigationHandler viewNavigationHandler;

    public void anyMethod()
    {
        //navigates to the view which is configured as default error-view
        this.viewNavigationHandler.navigateTo(DefaultErrorView.class);
    }
}

However, in case of JSF you have to ensure that you are at a valid point in the JSF request-lifecycle for a navigation, because invocation gets transformed to a std. (implicit) JSF navigation.

Using @Matches

This annotation is currently not integrated. [TODO]

Using ViewConfigResolver

If you would like to query view-meta-data yourself (for whatever reason), you can do that with ViewConfigResolver.

@RequestScoped
public class ApiDemoBean
{
    @Inject
    private ViewConfigResolver viewConfigResolver;

    public String getViewId(Class<? extends ViewConfig> viewConfigClass)
    {
        return viewConfigResolver.getViewConfigDescriptor(viewConfigClass).getViewId(); //or #getPath
    }

    public String getPath(Class pathConfigClass)
    {
        return viewConfigResolver.getConfigDescriptor(pathConfigClass).getPath();
    }

    public List<ConfigDescriptor<?>> getAllFolderDescriptors()
    {
        return viewConfigResolver.getConfigDescriptors();
    }

    public List<ViewConfigDescriptor> getAllPageDescriptors()
    {
        return viewConfigResolver.getViewConfigDescriptors();
    }

    public ViewConfigDescriptor getCurrentViewConfig()
    {
        return viewConfigResolver.getViewConfigDescriptor(FacesContext.getCurrentInstance().getViewRoot().getViewId());
    }

    public Class<? extends ViewConfig> getCurrentViewConfigClass()
    {
        return viewConfigResolver.getViewConfigDescriptor(FacesContext.getCurrentInstance().getViewRoot().getViewId()).getConfigClass();
    }
    //...
}

For folders it's optional to implement the ViewConfig interface, therefore you see 2 different types of API. #getConfigDescriptor as the general API and #getViewConfigDescriptor which is specific for pages (which have to implement the ViewConfig interface).

Besides translating a config class to the final path of the folder or page, it's possible to get the implicitly as well as explicitly configured (view-)meta-data and get and/or execute configured callbacks.

Advanced API usages

[TODO]

Creating Custom Meta-Data via @ViewMetaData

[TODO]

Creating Custom Callbacks via @ViewMetaData

[TODO]

Creating Custom inline Meta-Data via @InlineViewMetaData

[TODO]

View-Config SPI

[TODO]

Activation of custom naming conventions

Support of EAR deployments

Before using features described by this page, please ensure that you are aware of DELTASPIKE-335 and the corresponding impact.

Hints

Using errorView, DefaultErrorView or ViewNavigationHandler will fail with Weld versions below 1.1.10 due to WELD-1178.