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.

Monday, October 27, 2008

Using OpenLDAP as security provider in WebLogic

The post of Frank Nimphius on OTN over using OID as security provider in WebLogic inspired me to use OpenLDAP instead of OID. I will be using the standard LDAPAuthenticator for OpenLDAP too. Here are my steps to make it work.

Here is the ldif file I used.

First we create a organisation unit called groups

Do the same for persons
Add some persons to the persons organisation unit.

We can create a new group called ICT in the groups organisation unit and add the just created persons as member attributes.

Go to the default security realm and a new LDAPAuthenticator provider called OpenLDAP

Select the OpenLDAP provider and go to the Provider Specific tab where we will change some properties.

These are the openldap settings
  • User Name Attribute: sn
  • Principal: o=sgi,c=us
  • Enable Propagate Cause For Login Exception
  • Host: localhost
  • User Object Class: person
  • Static Member DN Attribute: member
  • Group From Name Filter: (&(cn=%g)(objectclass=groupofNames))
  • Static Group DNs from Member DN Filter: (&(member=%M)(objectclass=groupofNames))
  • Enable Use Retrieved User Name as Principal
  • Credential: your ldap password
  • Confirm Credential: your ldap password
  • Group Base DN: ou=groups, o=thecompany, o=sgi,c=us
  • User From Name Filter: (&(sn=%u)(objectclass=person))
  • Static Group Name Attribute: cn
  • User Base DN: ou=persons, o=thecompany, o=sgi,c=us
  • Static Group Object Class: groupofNames
Restart Weblogic and go the users and groups tab of the default security realm

Saturday, October 25, 2008

Oracle Service Bus 10.3

Oracle has just released the new Oracle Service Bus ( AquaLogic Service Bus ) which is a combination of the Oracle ESB and BEA AquaLogic Service Bus. So let's install it and give it a testdrive. ( I am a Soa Suite expert so I am very curious to see how aqualogic works) .
In this blog I will make a small ESB example where I use a JMS Queue and a Web service ( this are proxy services ) as input and the Service Bus will route these messages to my local C or D drive ( these are my business services). In this example I will only use the OSB console to configure my project, off course you can do it too in the eclipse workshop.
First we have to start the Service Bus Console.

use this url http://localhost:7021/sbconsole/ weblogic as username and password. Now we can go the project explorer where we will create a new Project called TestDrive.
Click the just created TestDrive project. In this project we will create two folders called wsdl and xsd.
We can now add a wsdl to the wsdl folder ( this wsdl is use for the proxy service) and a xml schema to the xsd folder. The xml we use in this TestDrive is based on this schema

Let add the business functions first. Create a new resource and select business service. Use as service type "Any XML Service"

Use File as protocol and add an file endpoint file:///c:/temp.
Do the same for the D drive business service.

We can add a JMS proxy service which retrieves a message from a queue and routes this message to to the C or D drive business service. Create a new resource and select the proxy service. Where we will use "Message Service" as service type
Now we can select the xml schema and the right element where the message is based on
Use JMS as protocol and as endpoint I use jms://localhost:7021/TestCF/TestQ . TestCF is the jms connection factory and TestQ is my demo queue. ( you can create these jms resources in the weblogic console)
Let add some routing to the JMS proxy
Click the JMSproxy and add "Add Route"
Edit this route
Add a routing table because I want to check an element in the message and then decides which business services I need use.
Click the expression and go to the variables structures where I select the body and the recipient element. From the property inspector I copy this value to the field above
If the receiver element has a particular value then route this message to the D drive
We have to add a default case. This case is used when the first expression is not valid.

Route the default case to the C drive business service and we are finished with the JMS proxy. Activate the project so we can test our services


Just add a message to the queue or launch the test console of the JMSproxy.

The next step is to to add a Web Service Proxy and add some routing to the C drive business service. Create a new proxy service resource and use the wsdl we uploaded in one of the first steps and select the consume operation.
Use the ws protocol, later we can use http://localhost:7021/TestDrive/WSproxy to test our ws or to retrieve the wsdl.
Add some routing to this proxy service. This time we only use the c drive business service so I can use Routing and not routing table.
Use the c drive business service

Activate the project and we are finished with our TestDrive project.

Conclusion: the Oracle Service Bus 10R3 is very easy to work with ( off course you need to have some basic knowledge about wsdl, xsd , jms ) and it was nice to see that you only have to use the sbconsole application to create the whole project. So the next time I will test the Oracle BPEL integration, Oracle Workshop, Data service providers, Performance and some complex routings.