Monday, January 28, 2013

Populating request attributes in OIM 11g R2 Part II - UI Customization

This is the second post of a two-post series about pre-populating requests in OIM 11g R2. The first post is available here. This post is also part of OIM 11g Academy Series.

The approach describe in this post is more sophisticated when compared to the pre-populate plug-in described in the previous post. The emphasis here is UI interaction. It is also important to mention that this approach does not work for requests created through the APIs, it works only for UI based requests. Another difference is that while the pre-populated plug-ins are specific to request attributes, this approach is application instance specific. In other words, each different application instance request form will require a different customization.

There is no preferred approach on pre-populating a request. Both approaches are valid and technically feasible. They both have advantages and disadvantages and the decision on which one to take must be based on requirements and also on long term maintenance. It is also important to mention that the approaches do not exclude each other, in other words, they can be both used in an OIM implementation if this is the best way.

The UI approach leverages the features provided by ADF and the UI Composer. It consists of a managed bean that gets invoked when the end user clicks on a custom button. The managed bean must be deployed to the OIM customization placeholder library (oracle.iam.ui.custom-dev-starter-pack.war) , and the button (here on called Prepopulate button) is part of the UI customization and must be manually added to the page through Composer.

The managed bean code is responsible for getting the information to be populated in the request form. It uses OIM APIs to get the beneficiary information from the request and from OIM user management layer, and it uses JSF/ADF APIs to update the request form UI components.

Below some references that can be used to get a better understanding of this post:

The steps below describe how to get the managed bean and the UI customization in place.

1. Coding the managed bean

The managed bean must be coded in a JDeveloper ADF View-Controller project. So the first step is to create the JDev application workspace and project.

With the project created, the second step is to create a plain Java class. In this example the complete class name is com.oracle.demo.iam.prepop.view.PrePopulateMBean. This class will have:
  • Two member variables that will hold references to the UI components: the custom Prepopulate button and its parent container
  • Accessor methods (get and set) for the variables above
  • An action listener type method to be invoked when the end user clicks on the custom Prepopulate button
  • A method that returns a boolean to will determine when the custom Prepopulate button must be disabled
The excerpt (the package and import statements were suppressed) below shows the custom code created in this example:

public class PrePopulateMBean {

    private UIComponent rootPanel;
    private UIComponent prepopulateButton;

    public PrePopulateMBean() {
        super();
    }

    public void setRootPanel(UIComponent rootPanel) {
        this.rootPanel = rootPanel;
    }

    public UIComponent getRootPanel() {
        return rootPanel;
    }

    public void setPrepopulateButton(UIComponent prepopulateButton) {
        this.prepopulateButton = prepopulateButton;
    }

    public UIComponent getPrepopulateButton() {
        return prepopulateButton;
    }

    public boolean isPrepopulateButtonRendered() {
        
        boolean ret = false;   
        RequestFormContext requestFormContext = RequestFormContext.getCurrentInstance();
        if (requestFormContext != null) {
            
            boolean isActionRequest   = (requestFormContext.getActionType() == RequestFormContext.ActionType.REQUEST);
            boolean singleUserRequest = false;

            if (requestFormContext.getBeneficiaryIds()!=null) {
                singleUserRequest = (requestFormContext.getBeneficiaryIds().size() == 1);
            }
            ret = isActionRequest && singleUserRequest;
        }    
        return (ret);            
    }

    public void actionListener(ActionEvent e) {

        if (e.getSource().equals(prepopulateButton)) {
            
            RequestFormContext requestFormContext = RequestFormContext.getCurrentInstance();
            List<String> beneficiaryIds = requestFormContext.getBeneficiaryIds();
            
            if (beneficiaryIds.size() == 1) {
                
                try {
                    User user = getUser(beneficiaryIds.get(0));
                    FacesUtils.setAttributeBindingValue("UD_OID_USR_FNAME__c", user.getFirstName());
                    FacesUtils.setAttributeBindingValue("UD_OID_USR_LNAME__c", user.getLastName());

                } catch (NoSuchUserException f) {
                    f.printStackTrace();
                } catch (UserLookupException f) {
                    f.printStackTrace();
                }
            }
        }
        FacesUtils.partialRender(rootPanel);
    }

    private User getUser(String userKey) throws NoSuchUserException, UserLookupException {
        
        UserManager userMgr = OIMClientFactory.getUserManager();
        
        HashSet<String> searchAttrs = new java.util.HashSet<String>();
        searchAttrs.add(AttributeName.USER_LOGIN.getId());
        searchAttrs.add(AttributeName.LASTNAME.getId());
        searchAttrs.add(AttributeName.FIRSTNAME.getId());
        
        return userMgr.getDetails(userKey,searchAttrs, false);
    }
}

Some details about the code :

- The 'isPrepopulateButtonRendered' method returns 'true' if a RequestContext is available and if there is only one request beneficiary. The check on the 'RequestContext' availability is required to avoid issues at customization time. This method is invoked when the custom Prepopulate button is loaded or its container refreshed.
- The 'actionListener' method executes a user search in OIM (by invoking the 'getUser' method) using the request beneficiary information, then it directly sets values on the UI components  'UD_OID_USR_FNAME__c' and 'UD_OID_USR_LNAME__c' with the information returned from the user search, and then invokes a partial rendering on the 'rootPanel' (this is the panel that holds the custom button and the request form). The partial rendering will make the values show up in the respective fields. It is important to mention here that this custom code contains a direct reference do the UI components and that these direct references can be found by exporting the Sandbox XML. This method is invoked when the custom Prepopulate button is loaded or its container refreshed.
- The 'FacesUtil' class is responsible for rendering UI changes. Its source code is available here


The PrePopulateMBean class must be declared as a managed bean in the JDev project. This declaration makes the MBean available to OIM UI so it can be invoked through the use of EL expressions. The image below shows this configuration


The View-Controller project must be deployed as an 'ADF Library Jar File'. This type of deployment can be easily created in JDeveloper through the deployment profiles option. The deployment will generate a Jar file, and such file must be copied into OIM placeholder library.

The placeholder library is the file oracle.iam.ui.custom-dev-starter-pack.war. This file is available along with the other OIM application packages (EARs and WARs) at $OIM_ORACLE_HOME/server/apps folder.

Before doing any modification to this file, create a backup of it.

To deploy the custom code using a ZIP tool (like 7-Zip) the steps are:
  1. Copy the  oracle.iam.ui.custom-dev-starter-pack.war to a temporary location
  2. Open the oracle.iam.ui.custom-dev-starter-pack.war
  3. Add the custom jar file to the 'WEB-INF/lib' folder. If the 'lib' folder does not exist, create it
  4. Save the oracle.iam.ui.custom-dev-starter-pack.war file
  5. Copy the oracle.iam.ui.custom-dev-starter-pack.war file back to its original location under $OIM_ORACLE_HOME/server/apps
  6. Stop OIM managed server
  7. In WebLogic administration console, update the 'oracle.iam.ui.custom' library deployment and activate the changes
  8. Start OIM managed server
The JDeveloper application workspace containing both the UI customization and the pre-populate plug-in (from the previous post of this series) projects is available here.

2. Customizing the UI

Like any other UI customization, the first step is to create a Sandbox. In this example the Sandbox was named 'RequestPrePop'. Once the Sandobox is created and activated:

- navigate to the Catalog
- search for the specific application instance to be customized (in this example the application instance is called 'Local OID'), add it do the shopping cart and hit the 'Checkout' button
- click on the 'Customize' link on the top right
- on the page top-left click on 'View' and then 'Source'
- on the page, where you see the 'Cart Items' and 'Details' sections, click very close to the 'Details' label. Make sure that the 'showDetailHeader:Details' component is selected

 
- on the top left, click on the 'Edit' link
- on the popup, edit the 'Binding' property and configure the following EL using the 'Expression Builder' #{backingBeanScope.prepopMBean.rootPanel} - this bind will make the UI invoke the 'setRootPanel' method in the custom managed bean. Hit 'Ok' button to save and close the edit popup




- still with the 'showDetailHeader:Details' component selected, click on the top left 'Add Content' link
- scroll the pop up all the way down and open the 'Web Components' section
- click on the 'Add' link on the right of the 'Command Toolbar Button' component


- now there is a big new button on the 'Details' section. This is going to be the custom 'Prepopulate'  button. Click on in and then on the 'Edit' link on the page top left
- edit the 'Text' property and set 'PrePopulate' as label
- edit the 'Binding' property and configure the following EL using the 'Expression Builder' #{backingBeanScope.prepopMBean.prepopulateButton} - this bind will make the UI invoke the 'setPprepopulateButton' method in the custom managed bean. Hit 'Ok' to save and close the popup.


- edit the 'Disabled' property and configure the following EL using the 'Expression Builder' #{!backingBeanScope.prepopMBean.prepopulateButtonRendered} - this will make the UI invoke the 'isPrepopulateButtonRendered' method in the managed bean.Hit 'Ok' to save and close the popup


- still on the popup, click on the 'Style' tab. Set the 'Width' property to 100, and the 'Margin - Left' property to 100. Hit 'Ok' to save and close the popup. This configuration will make the PrePopulate button look nice and well placed in the UI



- Exit the customization mode by clicking on the top right 'Close' link

At this point, the customization is almost done. Because OIM Composer does not provide access to the whole set of properties of an UI component, a manual configuration is required at this point:

- navigate to the 'Sandbox' page by clicking on the top 'Sandobxes' link
- from the list, make sure that the sandbox created for this customization is not active and export it
- save the ZIP file in the local file system
- explode the ZIP file
- open, in a plain text editor, the XML file corresponding to the customization. In this example, the file is oracle/iam/ui/runtime/form/view/pages/mdssys/cust/site/site/OIDUserFormCreateForm.jsff.xml
- at the of the file, there will be a section defining the custom Prepopulate button. Something like the excerpt below:

<af:commandToolbarButton xmlns:af="http://xmlns.oracle.com/adf/faces/rich" id="e8829502064" binding="#{backingBeanScope.prepopMBean.prepopulateButton}" text="PrePopulate"

- add the 'actionListener' property to the custom Prepopulate button: 
actionListener="#{backingBeanScope.prepopMBean.actionListener}



- save the file and repackage the ZIP contents. Make sure that the path is preserved when repacking the contens
- back to OIM UI, still on the 'Sandboxes' page, hit 'Import Sandbox' and import the ZIP file. Make sure that the sandbox is not active when importing it back
- activate the sandbox



2. Test the customization

Now that the sandbox contains all the customizations required it can be tested. Navigate to the Catalog, find the application instance and add it do the shopping cart. In the cart summary page, you should see the custom 'Prepopulate' button, when clicking on it, the First Name and Last Name fields will be updated with the beneficiary's information:


Once you have tested the customization and it works fine, the 'Sandbox' is ready to be published.

6 comments:

  1. hi,

    i am stuck at second step... not getting "Local OID" when i search!!!

    ReplyDelete
    Replies
    1. You have to search for an Application Instance that has been created in your environment.

      Delete
    2. Hi Daniel,

      When user login for the first time via user console, oim redirects on change password and set challenge questions page. Can i customize that page e.g. adding a new component or so, Ff yes then how to access customize that page ??
      Please reply. Thanks in Advance.

      Delete
    3. Manoj,

      Due to some missing authorization policies, it is not possible to customize that page via sandbox route. That might be fixed in upcoming bundle patches.

      Thanks

      Delete
  2. Hi Daniel,

    How can I pre-populate the child table of the Application Instance Form ?

    ReplyDelete
    Replies
    1. That is a bit more complicated than populating the main form, especially if you have more than one child form. Unfortunately I do not have an example for that.

      Delete

Note: Only a member of this blog may post a comment.