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.
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:
Advantages which are planned for later (= currently not supported):
If you are still not convinced, you just have to try it. You will see how your daily workflow benefits from it pretty soon.
While reading this section keep the following simple rules in mind:
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; }
@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:
To customize it you can use @Folder#name, @View#basePath, @View#name and @View#extension (or you register custom NameBuilders inline or globally).
The rules are pretty simple. You will get what you write. There are only two additional features:
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:
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:
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).
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; } }
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; } }
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 { } } }
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() { //... } }
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).
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).
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.
This annotation is currently not integrated. [TODO]
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.
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
Before using features described by this page, please ensure that you are aware of DELTASPIKE-335 and the corresponding impact.
Using errorView, DefaultErrorView or ViewNavigationHandler will fail with Weld versions below 1.1.10 due to WELD-1178.