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.
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.
Hi,
ReplyDeleteThanks for this effort,can i use the same with jdeveloper 10g
regrads
Hi, what is the username and password to login to the site?
ReplyDeleteThe prompt also says "The site says : jazn.com"
Hi, its ok, i got it, the username and password is the same login for the weblogic console.
ReplyDeleteI 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?
Hi Paul,
ReplyDeleteCan 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
Hi,
ReplyDeleteThis 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
Hi Edwin,
ReplyDeleteWe 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
Hi Sita.
ReplyDeletethe 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
Hi Edwin,
ReplyDeleteThank 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
Ok Sita,
ReplyDeleteyou 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
Hi Edwin,
ReplyDeleteThank 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
Hi,
ReplyDeleteFor 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
Hi Edward,
ReplyDeleteThanks 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
Hi,
ReplyDeleteDo 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
Hi, Edwin.
ReplyDeleteThank 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
Hi,
ReplyDeleteI 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
Hi.
ReplyDeleteThanks! 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?
Hi good job,
ReplyDeletecan you make a testcase then i will take a look , email biemond at gmail dot com
thanks Edwin
How to setup scottDS datasource for this projecty
ReplyDeleteHi, thanks for tips and all the information that you share with us.
ReplyDeleteI´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.
Hi,
ReplyDeletejust 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
Hi,
ReplyDeleteI´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
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.
ReplyDeleteHi,
ReplyDeleteCould 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.
Hi TestUpfna,
ReplyDeleteIn your viewobject did you enable the java part. you can generate a view or row impl in the java section of your viewobject.
Hi Edwin,
ReplyDeleteThanks 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.
Hi
ReplyDeleteplease send your model and viewcontroller project to biemond at gmail dot com.
then i can take a look.
Hi,
ReplyDeleteThe 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
Hi,
ReplyDeleteIt 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
thanks for your useful articles
ReplyDeletebut 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
I am using jdeveloper 11.1.2
ReplyDeleteI 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
Hi,
ReplyDeleteyou 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
Hi Edwin,
ReplyDeletegreat 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?
Hi,
ReplyDeleteI 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
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?
ReplyDeleteHi Edwin,
ReplyDeleteI 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.
Hi,
ReplyDeleteDon't know, but maybe there is some skinning css thing you can set.
thanks
Hi
ReplyDeleteWhen 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
Hi,
DeleteI 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
Hi Edwin,
ReplyDeleteWhile 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
Hi,
Deleteyou 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
Hi Edwin,
ReplyDeleteYou 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
Hi,
DeleteIndeed 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
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.
ReplyDeleteHello Edwin
ReplyDeleteI also need to create a database driven menu which will have a tree structure. Facing the same problem as Pratik said in above comment.
Hi Edwin,
ReplyDeleteIn 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)?
Hi Edwin,
ReplyDeleteIn 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)?