Pages

Saturday, November 1, 2008

Dynamic Menu based on Roles ( Database)

I had a request to re-make my old dynamic menu example. This time the menu is based on roles and the menu / role definitions are stored in the database. I will use ADF security for the authentication and a menu bean retrieves the user roles from ADFContext. With these roles the bean can build a new menu.
Here I am user edwin which has the administrator role

Now I am the user scott which does not have Menu 2 and page 2 in Menu 1
This is the data model I used for this example.
  • Menu table is used for the menus in the menubar.
  • Menu_items table is used for the menu items in the menu. the action column must match the control flow case in the unbounded taskflow.
  • Roles table must match the roles used in ADF security
  • Role_menu_items is a intersection table of roles and the menu items, so the bean can determine which menu items are used for the menu.
Every menu items in the dynamic menu must be a view in the unbounded task flow and has a control flow case. The name of the control flow case must match the action column in the menu_items tables.
Here you can see how my menu_items table looks like.


Here is the dynamic menu bean code

package nl.ordina.menu.backing;

import java.io.IOException;

import java.util.Iterator;
import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.MethodExpression;
import javax.el.ValueExpression;

import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.PhaseEvent;

import javax.servlet.http.HttpServletResponse;

import nl.ordina.menu.model.dataaccess.MenuItemsViewImpl;
import nl.ordina.menu.model.dataaccess.MenuItemsViewRowImpl;
import nl.ordina.menu.model.services.MenuModuleImpl;

import oracle.adf.share.ADFContext;
import oracle.adf.view.rich.component.rich.RichMenu;
import oracle.adf.view.rich.component.rich.RichMenuBar;
import oracle.adf.view.rich.component.rich.nav.RichCommandMenuItem;

public class MenuBean {

private RichMenuBar initMenu;

public void createMenus(PhaseEvent phaseEvent) {

// check the menu is already added
boolean addMenu = true;
for (Iterator iterator = initMenu.getChildren().iterator(); iterator.hasNext();) {
UIComponent component = (UIComponent) iterator.next();
if ( component.getId().startsWith("menuId")){
addMenu = false;
}
}
if (addMenu) {

// get roles
String[] roles = ADFContext.getCurrent().getSecurityContext().getUserRoles();

// get application module
MenuModuleImpl menuAM = getAm();
MenuItemsViewImpl menuView =
(MenuItemsViewImpl)menuAM.getMenuItemsView();
menuView.executeQuery();
while (menuView.hasNext()) {
MenuItemsViewRowImpl menuItem = (MenuItemsViewRowImpl)menuView.next();

// check if the user has this role
boolean roleFound = false;
for (int i = 0 ; i < roles.length ; i++ ) {
if ( roles[i].equalsIgnoreCase(menuItem.getRoleName()) ){
roleFound = true;
}
}

if (roleFound) {
Boolean menuFound = false;
RichMenu menu = new RichMenu();
String menuId = "menuId" + menuItem.getMenuId().toString();

// check if the main menu is already added
for (Iterator iterator = initMenu.getChildren().iterator();
iterator.hasNext(); ) {
UIComponent component = (UIComponent)iterator.next();
if (component.getId().equalsIgnoreCase(menuId)) {
menuFound = true;
menu = (RichMenu)component;
}
}
if (!menuFound) {
// new main menu
RichMenu newMenu = new RichMenu();
newMenu.setId(menuId);
newMenu.setText(menuItem.getMenuName());
newMenu.setIcon(menuItem.getMenuIcon());
initMenu.getChildren().add(newMenu);
menu = newMenu;
}

Boolean menuItemFound = false;
String menuItemId = menuItem.getName();

// check if the menu item is already added

for (Iterator iterator = menu.getChildren().iterator();
iterator.hasNext(); ) {
UIComponent component = (UIComponent)iterator.next();
if (component.getId().equalsIgnoreCase(menuItemId)) {
menuItemFound = true;
}
}
if (!menuItemFound) {
RichCommandMenuItem richMenuItem = new RichCommandMenuItem();
richMenuItem.setId(menuItemId);
richMenuItem.setText(menuItem.getName());
richMenuItem.setActionExpression(getMethodExpression(menuItem.getAction()));
richMenuItem.setIcon(menuItem.getIcon());
menu.getChildren().add(richMenuItem);
}
}
}
menuView.remove();
}
}

public void setInitMenu(RichMenuBar initMenu) {
this.initMenu = initMenu;
}

public RichMenuBar getInitMenu() {
return initMenu;
}

private MethodExpression getMethodExpression(String name) {
Class[] argtypes = new Class[1];
argtypes[0] = ActionEvent.class;
FacesContext facesCtx = FacesContext.getCurrentInstance();
Application app = facesCtx.getApplication();
ExpressionFactory elFactory = app.getExpressionFactory();
ELContext elContext = facesCtx.getELContext();
return elFactory.createMethodExpression(elContext, name, null,
argtypes);
}

private MenuModuleImpl getAm() {
FacesContext fc = FacesContext.getCurrentInstance();
Application app = fc.getApplication();
ExpressionFactory elFactory = app.getExpressionFactory();
ELContext elContext = fc.getELContext();
ValueExpression valueExp =
elFactory.createValueExpression(elContext, "#{data.MenuModuleDataControl.dataProvider}",
Object.class);
return (MenuModuleImpl)valueExp.getValue(elContext);
}

public String doLogOut() throws IOException{
ExternalContext ectx = FacesContext.getCurrentInstance().getExternalContext();
HttpServletResponse response = (HttpServletResponse)ectx.getResponse();
FacesContext fctx = FacesContext.getCurrentInstance();
String currentPage = "/faces/about";// + fctx.getViewRoot().getViewId();
String url = ectx.getRequestContextPath()+"/adfAuthentication?logout=true&end_url=" + currentPage;
try {
response.sendRedirect(url);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}

}

the next step is to create jsf template where we will add a menubar which binds the menu bean.

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
<jsp:directive.page contentType="text/html;charset=windows-1252"/>
<af:pageTemplateDef var="attrs">
<af:panelStretchLayout topHeight="49px">
<f:facet name="center">
<af:facetRef facetName="body"/>
</f:facet>
<f:facet name="top">
<af:panelGroupLayout layout="scroll"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
<af:facetRef facetName="menu"/>
<af:menuBar id="menu" binding="#{Menu.initMenu}">
<af:commandMenuItem text="Log off" action="#{Menu.doLogOut}"/>
</af:menuBar>
</af:panelGroupLayout>
</f:facet>
</af:panelStretchLayout>
<af:xmlContent>
<component xmlns="http://xmlns.oracle.com/adf/faces/rich/component">
<display-name>templateMenu</display-name>
<facet>
<facet-name>menu</facet-name>
</facet>
<facet>
<facet-name>body</facet-name>
</facet>
</component>
</af:xmlContent>
</af:pageTemplateDef>
</jsp:root>

Create a new view in the unbounded task flow and create a new jsf page based on the just created jsf template. Be sure to make a new control flow case from the wildcard control flow case to this view and give this control flow case a name.

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
<jsp:directive.page contentType="text/html;charset=windows-1252"/>
<f:view beforePhase="#{Menu.createMenus}">
<af:document>
<af:form>
<af:pageTemplate viewId="/templates/templateMenu.jspx"
value="#{bindings.pageTemplateBinding}">
<f:facet name="menu"/>
<f:facet name="body">
<af:panelHeader text="Page 2"/>
</f:facet>
</af:pageTemplate>
</af:form>
</af:document>
</f:view>
</jsp:root>

Very important to add the createMenus method in the beforephase of the view component. Add this page to the menu items table and use the control flow case name in the action column.

The last step is to configure ADF Security ( the roles in ADf security must match the roles in the database ) and voila we are finished.
Here is the example project and the tables script.

I used the following users in this example. edwin ( password Welcome01 ) which has the administrator and user role. The second user is scott ( pasword Welcome01 ) which has the user role.

46 comments:

  1. Hi,
    Thanks for this effort,can i use the same with jdeveloper 10g
    regrads

    ReplyDelete
  2. Hi, what is the username and password to login to the site?

    The prompt also says "The site says : jazn.com"

    ReplyDelete
  3. Hi, its ok, i got it, the username and password is the same login for the weblogic console.

    I got another problem, when i try to load page1, i got a JDBC connection logon denied error.

    I have already change the database settings in jdev to point to my own database with menu tables all added.

    Do you know where else to change?

    ReplyDelete
  4. Hi Paul,

    Can you open the adf bc application module configuration and check if the datasource url is matching your database connection.

    or can you post the stacktrace

    thanks Edwin

    ReplyDelete
  5. Hi,

    This is interesting post and it's really helpful in understanding the database driven menus based on the roles.

    Could you please provide some inputs for creating database driven menu using JDeveloper 10.1.3.

    Thanks and Regards,

    S R Prasad

    ReplyDelete
  6. Hi Edwin,

    We are rewriting the existing Oracle FORMS application using ADF. The existing application uses the menus and roles from the database. The expectation is to reuse the existing database schema (with minor changes to match with web based development) as both FORMS environment and ADF environments co-exists for some more time.

    As per the 10.1.3 ADF developer guide all the menu navigation should be specified in the faces-config.xml file. Is there any possibility to map the menu navigation with the existing database schema.

    We may not move to the JDeveloper 11g immediately as we did not find any alternative to integrate weblogic and Oracle AS 10.1.2 (Forms Server).

    Thanks and Regards,

    S R Prasad

    ReplyDelete
  7. Hi Sita.

    the new version of forms will run on wls or the new oc4j 11g. Please ask oracle for the details

    in wls you have a security provider ( sql authenticator) which you can use on your tables. I promise you don't need to change anything. These tables are used for authentication and authorization. ADF security in 11g will use these user and roles.

    and in jsf you can use the rendered and disabled attribute of a menu component to disable it.

    just use securityContext.isUserInRole ( role1,role2)


    in 10.1.3 you can do the same then you have to forward to page instead of a refresh of a region . please take a look at jheadstart this can give you a hint how it is done. but in 10.1.3 you have to load the whole page.

    in 10.1.3 you have to add actions to faces-config.xml , maybe you can put the action in your tables.

    thanks

    ReplyDelete
  8. Hi Edwin,

    Thank you for immediate response. It is really difficult to convince management to upgrade the forms environment to oc4j 11g or weblogic server immediately as it require testing in the forms environment.

    At present we are using Oracle Signle Signon for authentication and using custom Role Based authorization for displaying the menus in the forms environment (don't have much knowledge in the forms environment). As per my knowledge there is no immediate solution to integrate Oracle AS 10.1.2 and Oracle Weblogic using SSO infrastructure. By considering all these factors we are planning to continue with JDeveloper 10.1.3 (We may move to 11g in another 6-8 months once the forms team is comfortable in migrating forms to 11g environment).


    By considering these factors, could you please suggest some pointers/code to make use of existing database schema for generating dynamic menus in JDeveloper 10.1.3 environment.

    Thanks and Regards,

    S R Prasad

    ReplyDelete
  9. Ok Sita,

    you can make use of oid and ADF , make sure you have the logged in user and its roles in a bean so you can use in the rendered or disabled field.

    If I want to make a dynamic menu I should use a adf region which you can include in every page. Make an adf bc application module with a viewobjects which contains the sql , put this am in every am ( nested ) and now this viewobject in a for each loop to create tab or menu.

    Why don't you make an hard menu or tabs in an ADF region and use the render attribute on a menu to show it or not ( depends on the roles it has )

    and you can download an evaluation version of jheadstart where you create an webapp with a custom authentication ( this retrieves the menu's from tables ) and look how this is down.

    thanks Edwin

    ReplyDelete
  10. Hi Edwin,

    Thank you for the update. It seems the regions solutions is a right match with our requirement.

    I verified our forms database schema for creating the menu structure. The existing database schema matches with the schema published in this article. Do you have any examples in JDeveloper 10.1.3 for building the menu with the current schema.

    I am unable to complete the demo with the dynamic menu from the database schema. If possible could you please provide some skeleton code for JDeveloper 10.1.3 to build the menu structure with the current database schema in this example.

    Thanks and Regards,

    S R Prasad

    Thanks and Regards,

    S R Prasad

    ReplyDelete
  11. Hi,

    For me it is too much work to build it in 10.1.3 but if you download jheadstart and make test page with dynamic menu and custom autorization.
    then you have a best practice

    http://www.oracle.com/technology/products/jheadstart/index.html

    thanks

    ReplyDelete
  12. Hi Edward,

    Thanks for your inputs. We finally retreived the menu information from the database.

    We have to create simple menu handlder and initializeMenuList() method to retreive the menu information from the database.

    List _menuList = initializeMenuList();
    TreeModel _treeModel =
    new ChildPropertyTreeModel(_menuList, "children");
    MenuModel _menuModel =
    new ViewIdPropertyMenuModel(_treeModel, "children");



    Do you have any idea about integrating Oracle Forms and Oracle ADF applications together using Oracle SSO.




    Thanks and Regards,

    S R Prasad

    ReplyDelete
  13. Hi,

    Do you have any idea about integrating Oracle Forms and Oracle ADF applications together using Oracle SSO.

    You can ask Wilfred, he is an expert in Forms , ADF and OC4J

    http://www.oratransplant.nl/

    thanks Edwin

    ReplyDelete
  14. Hi, Edwin.

    Thank you for your post. It's very useful.

    It works well at first pagein.

    But I have some problem. When I press the refresh (F5) or I pass to other pages through the dynamic menu, I receive an exception:

    "Exception while processing expression #{MainMenuAdapter.createMenus} for attribute beforePhase
    Exception
    javax.el.ELException: java.lang.NullPointerException"

    Do you have any idea, how to resolve it?

    Thanks in advance

    ReplyDelete
  15. Hi,

    I upgraded my test case to jdev R1 PS1 and retested it again, it works perfectly.

    for the testcase you need to create the menu and roles tables and make a user with an administrator and user role ( do this in the wls console )

    and a jdbc/scottDS datasource.

    hope this helps

    ReplyDelete
  16. Hi.

    Thanks! Work great. I'm expanded on your idea and am running into a problem.

    One of the extra features is a public flag for a menu item. In this case, menu items that do not have security will be shown while others that are role based are not shown until the user has authenticated.

    While I've figured out how to modify the bean to rebuild the menu after login, it appears the beforephase call doesn't get called again after login. Any ideas?

    ReplyDelete
  17. Hi good job,

    can you make a testcase then i will take a look , email biemond at gmail dot com

    thanks Edwin

    ReplyDelete
  18. How to setup scottDS datasource for this projecty

    ReplyDelete
  19. Hi, thanks for tips and all the information that you share with us.

    I´m beginning with ADF (BC) and our team need to develop a fussion web application. Our previously development was in Forms and Reports 10g. But we want to follow the roadmap defined by Oracle.

    Now... I have some questions and I really appreciate that you can help me to give me a path or some answers. I´m a little confuse about why I need to register the application roles that I already had stored in db, I read the theory and I do not know if I need to do the same with users , ie for every user that I have in db tables I will need to register in wls and assign that users to certain application roles?

    I check your example an I see that you configure the required files to work with ADF security and I do not clarfy my ideas about this duplicity of information.

    Sorry for my English and thanks in advance.

    ReplyDelete
  20. Hi,

    just create an database connection scott in your project and run it.

    or in the wls console go to jdbc datasources and create one.

    thanks Edwin

    ReplyDelete
  21. Hi,

    I´m a little confuse about why I need to register the application roles that I already had stored in db,

    You have Database / roles in a table , Weblogic (enterprise) and Application roles. With using SQLauthenticator in wls you can make sure that the database and wls roles are the same. These are more global roles and can be used in every application of db schema. App roles can be mapped between the app and wls roles. App is on the app level and not on the container so you can have authorization differences between apps.

    if I need to do the same with users, ie for every user that I have in db tables

    Nop, just use the SQLAuthenticator and your are ready.

    so it is only needed for authorization.

    hope this helps

    ReplyDelete
  22. Thank you for response. I appreciate a lot all things that you do for the jdev community. Now I have things more clear, and I´ll try to use another of your examples that it´s in the post "Using database tables as authentication provider in WebLogic" I only hope that works with the last release of jdeveloper 11g and Integrated WLS.

    ReplyDelete
  23. Hi,
    Could you let me know how to use MenuItemsViewRowImpl in the managed bean. I tried different ways and i get only the compile error cannot find class.
    I generated the view object class as well and still its not seeing it.
    Thanks.

    ReplyDelete
  24. Hi TestUpfna,

    In your viewobject did you enable the java part. you can generate a view or row impl in the java section of your viewobject.

    ReplyDelete
  25. Hi Edwin,
    Thanks for replying inspite of your busy time. Yes,
    1. i created the view object, generated the view object class and 2. View Row implementation class through the java option,
    3. created a template with panel Menu Bar
    4. In the bindings(under advanced option) of the menu bar in the template,i did the edit and created the menu bean with the property initMenu ( scope of request) and copied and pasted your code and made small necessary modifications regarding names of app module, viewRowImpl name etc.
    Still when i compile the bean code, it says "cannot find class menuitemsviewRowImpl"
    i deleted the bean code and recreated , little tried anything i could think of but in vain, any light on this will be greatly appreciated.

    ReplyDelete
  26. Hi

    please send your model and viewcontroller project to biemond at gmail dot com.

    then i can take a look.

    ReplyDelete
  27. Hi,
    The createMenus method gets called for every page request and gets called for every phase event of that page, (i.e. 6 times for a page) once for every phase. Any idea why this happens or how can it be avoided?
    I want it to be called only once after login.
    Thanks,
    Mira

    ReplyDelete
  28. Hi,

    It is not that bad, I check for addMenu. If the code is Ok it should only add the menu once. But you can change it, so you can return even earlier.

    thanks

    ReplyDelete
  29. thanks for your useful articles
    but I didn't understand why and where you used the task flow in this example, maybe cause I didn't read your old DYNAMIC MENU article,
    if I must read it to understand this one, please give me the link of it


    thanks

    ReplyDelete
  30. I am using jdeveloper 11.1.2
    I added the menu to my template so it will appear on all pages using this template
    now in my requirement that there is users with more than one role
    when user log in to the application there is default role used and the menu of this default role is appear
    the new requirement that user can change the used role to another one in run time using choice component contains all user roles without logout and login

    that what i did in my code when user click on button OK that change the used role

    RichCommandButton commandBtn = (RichCommandButton)actionEvent.getSource();
    RichSelectOneChoice selectedRole = (RichSelectOneChoice)commandBtn.findComponent("roles_soc");
    String selectedValue = selectedRole.getValue().toString();
    DCIteratorBinding sciPlanVO1Iterator = getBindings().findIteratorBinding("MenuItemsVO1Iterator");
    sciPlanVO1Iterator.executeQuery();

    now when I try to check the used role in my application I find that it is the new role and that what i expected

    but the problem that the old menu stay and don't update to the new menu that related with new role

    ReplyDelete
  31. Hi,

    you need to do a ppr on the menu, so set clientcomponent on true and try to find the menu uicomponent and do a refresh.

    Or maybe you can set ppr ( changeventpolicy) on the iterator , this works on a table.

    thanks

    ReplyDelete
  32. Hi Edwin,
    great post.
    I was wondering if the menu bean is defined in the session scope and called only once after the log in, may be we can reduce the number hits to these bean.Because the menu will be static for each session. Is there any draw back to this approach? If session scope is possible, can you suggest how and when to call the create menu?

    ReplyDelete
  33. Hi,

    I don't think you don't need to worry about that , because I check if the menu already exists. And now it works on the authenticated user with his roles and menu items.

    maybe you can move it to application scope when everybody has the same menu

    thanks

    ReplyDelete
  34. Sorry I missed my point. I was asking you, whether this menu will be repopulated for each new jspx page. Because since it is in request scope, when user navigates to new page, the menu component will be destroyed, right?

    ReplyDelete
  35. Hi Edwin,
    I have one issue with the menu. When there are more than 14 child components for a menu a scroll button appears on to and bottom of the menu. I will have to use this button to scroll to lower parts of menu.
    This works fine if the menu is a root menu. But if it is coming inside a sub-menu then when I click on the scroll button, the menu is getting closed. Any way to solve this issue? I don't need the scroll option.

    ReplyDelete
  36. Hi,

    Don't know, but maybe there is some skinning css thing you can set.

    thanks

    ReplyDelete
  37. Hi
    When I run the sample application nothing happens when I press the menu logout button. The menu stays and I have access. What can be wrong ?
    Is it possible to remove menu and item from the rich menu bar ?
    Br Lene

    ReplyDelete
    Replies
    1. Hi,

      I think I use basic authentication and this authenticates me again with the next click.
      You need to make a custom login page and with a logout which can destroy the session and redirect to the login page.

      thanks

      Delete
  38. Hi Edwin,
    While opening page1 it shows the following error message

    While trying to lookup 'jdbc.scottDS' didn't find subcontext 'jdbc'. Resolved ''
    While trying to lookup 'jdbc.scottDS' didn't find subcontext 'jdbc'. Resolved ''

    After pressing OK page1 only shows a Log off button and
    Page 1
    No rows yet.

    Please help in this regard
    Thanks

    ReplyDelete
    Replies
    1. Hi,

      you need to create this scott jdbc connection in your jdeveloper workspace or add the datasource to your integrated weblogic container.
      Plus you need to have a database which has the scott schema.

      thanks

      Delete
  39. Hi Edwin,
    You haven´t protected the pages right? If i´m the administrator i can see all the pages.
    When i login if a user with restricted role and then change the url to let´s say page 3 i can see the page.

    How do you protect the pages using those roles?
    Thank you

    ReplyDelete
    Replies
    1. Hi,

      Indeed this is a menu based on the roles of the user and does not protect the pages. It must be used together with ADF Security , in there you can assign roles to pages and task flows.

      thanks

      Delete
  40. Hello Edwin...I need to create a database driven menu which will have a tree structure.How can I do that? Kindly have a look at this image to get an idea of what kind of menu I'm talking http://s20.postimg.org/638hfrrfh/Menu.jpg .The leaf Menu will be different JSF pages.

    ReplyDelete
  41. Hello Edwin
    I also need to create a database driven menu which will have a tree structure. Facing the same problem as Pratik said in above comment.

    ReplyDelete
  42. Hi Edwin,

    In ADF Jdev 11g: Can you brifly explain how menu can be built using data from database table( which has info about pages to be navigated)?

    ReplyDelete
  43. Hi Edwin,

    In ADF Jdev 11g: Can you brifly explain how menu can be built using data from database table( which has info about pages to be navigated)?

    ReplyDelete