Sonntag, 6. Dezember 2015

Programmatic integration with embedded LDAP

Tech stack

Implementation demonstrated in this blog is developed using JDeveloper – version 12.1.3.0.0

Use case

In any secured application, user and roles plays a significant role in driving UI behaviour. While you want certain functionality to be hidden from users not having certain roles/privileges, you may want to provide full access to certain types of users (typically application administrators).

Usually for an enterprise application, one goes with a specialized licenced product (like Oracle IDM) to take care of application user and role management. But when the application is in nascent stage and you don’t have a dedicated server configured with a security suite, the only way you can quickly build and test your features is leveraging the embedded LDAP which comes packaged with Oracle Weblogic Server.

This blog gives you a kick start on how to programmatically establish connection with the embedded LDAP and also provides basic APIs on searching a user and its roles.

Implementation steps

Step 1: Create an ADF application with Model and ViewController projects. Use the “Configure ADF Security…” wizard to secure the application. Opt for both authentication and authorization security.


Step 2: Model Project
a) Create IntegratedLDAPService class such that it returns singleton instance after establishing LDAP connection and implements the APIs to search user and roles.

 /**
     * Singleton method to get Service Helper instance
     * @return Singleton OID Service Helper
     */
    public static IntegratedLDAPService getInstance() {
        if (ldapServiceInstance == null) {
            ldapServiceInstance = new IntegratedLDAPService();
        }
        return ldapServiceInstance;
    }

    /**
     * Private constructor which sets up the Identity store connection
     * member variable*/
    private IntegratedLDAPService() {
        try {
            initIdStoreFactory();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Error initializing OID Factory:", e);
        }
    }

    private void initIdStoreFactory() {
        JpsContextFactory ctxFactory;
        try {
            ctxFactory = JpsContextFactory.getContextFactory();
            JpsContext ctx = ctxFactory.getContext();
            LdapIdentityStore idStoreService = (LdapIdentityStore) ctx.getServiceInstance(IdentityStoreService.class);

            ldapFactory = idStoreService.getIdmFactory();

            storeEnv.put(OIDIdentityStoreFactory.RT_USER_SEARCH_BASES, USER_BASES);
            storeEnv.put(OIDIdentityStoreFactory.RT_GROUP_SEARCH_BASES, GROUP_BASES);
            storeEnv.put(OIDIdentityStoreFactory.RT_USER_CREATE_BASES, USER_BASES);
            storeEnv.put(OIDIdentityStoreFactory.RT_GROUP_CREATE_BASES, GROUP_BASES);
            storeEnv.put(OIDIdentityStoreFactory.RT_GROUP_SELECTED_CREATE_BASE, GROUP_BASES[0]);
            storeEnv.put(OIDIdentityStoreFactory.RT_USER_SELECTED_CREATE_BASE, USER_BASES[0]);
        } catch (JpsException e) {
            e.printStackTrace();
            throw new RuntimeException("Jps Exception encountered", e);
        }
    }


Above code snippet implements singleton design to ensure that at any given time only one LDAP connection is initiated.


Above code snippet implements singleton design to ensure that at any given time only one LDAP connection is initiated.


Please note that the USER_BASES and GROUP_BASES objects depends on your domain configuration. For this sample, I am using the following values:


 /**
     * User base for embedded ldap
     */
    private final static String[] USER_BASES = { "ou=people,ou=myrealm,dc=DefaultDomain" };

    /**
     * Group base for embedded ldap
     */
    private final static String[] GROUP_BASES = { "ou=groups,ou=myrealm,dc=DefaultDomain" };


Here, “myrealm” is the default realm of the WLS and “DefaultDomain” is the default name of the domain created for my integrated WLS instance. If you have different values, then please update these variables accordingly.

The variable ldapFactory is a private variable which will be used to perform operations on the LDAP for any user/role operations as shown below:

Search User API
    public User searchUser(String userName) throws Exception {
        IdentityStore idStore = null;
        try {
            idStore = ldapFactory.getIdentityStoreInstance(storeEnv);

            return idStore.searchUser(userName);
        } catch (IMException e) {
            return null;
        } finally {
            try {
                if (idStore != null) {
                    idStore.close();
                }
            } catch (IMException e) {
                e.printStackTrace();
                throw new Exception("Error Closing Identity Store Connection during searchUser:" + userName +
                                    "in OID:" + e.getMessage(), e);
            }
        }
    }


Above API invokes the searchUser method to find and return the User object corresponding to a user name. If no result is found, it returns null.

Search Roles for User API
    public List getRolesForUser(User user) throws Exception {
        IdentityStore idStore = null;
        List eRoles = new ArrayList();

        try {
            idStore = ldapFactory.getIdentityStoreInstance(storeEnv);

            RoleManager rm = idStore.getRoleManager();
            SearchResponse resp = null;
            
            try {
                resp = rm.getGrantedRoles(user.getPrincipal(), false);
            } catch (ObjectNotFoundException onfe) {
                onfe.printStackTrace();
                return eRoles;
            }

            while (resp.hasNext()) {
                String name = resp.next().getName();
                EnterpriseRole eRole = new EnterpriseRole();
                eRole.setRoleName(name);
                eRoles.add(eRole);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("Error getting Roles for User:" + user.getUserProfile().getFirstName(), e);
        } finally {
            try {
                idStore.close();
            } catch (Exception e) {
                e.printStackTrace();
                throw new Exception("Error Closing Identity Store Connection during getting Roles for User:" +
                                    user.getDisplayName() + "in OID:", e);
            }

        }
        return eRoles;
    }
Above API finds and returns list of all the roles corresponding to the input User object.

Additionally, please ensure that the model project has imported all the following libraries for the project to get compiled:



b) Create an application module SecurityAM and generate its implementation class. Here we will write methods to be exposed for UI layer to consume. These methods will internally get the handle of IntegratedLDAPService class which will be one stop implementation of all LDAP related actions (establishing connection, searching user and roles etc.)

public class SecurityAMImpl extends ApplicationModuleImpl implements SecurityAM {

    IntegratedLDAPService ldapInstance = IntegratedLDAPService.getInstance();

    /**
     * This is the default constructor (do not remove).
     */
    public SecurityAMImpl() {
    }

    public User searchUser(String userName) {
        try {
            return ldapInstance.searchUser(userName);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    public List getRolesForUser(User user){
        try {
            return ldapInstance.getRolesForUser(user);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}


c) Expose the AMImpl methods to client interface



Please note that I have created EnterpriseRole and EnterpriseUser as java entities to handle data for users and roles. This is done because User and Role are interfaces exposed by IDM and in order to handle these types we need simple java classes.

Step 3: UI Project
a) Create a page fragment, task flow and a jspx page such that we have following as our end result:



In above page we have a text box where you can type in a user name (like weblogic) and a command button “Find”. Also there is a listView component below which displays all the roles assigned to the searched user. Refer the attached application for exact code structure.

Please note that since the application is secured, you need to provide resource grants to pages and task flows in the jazn xml.

Since the application is secured, you can user any user defined in embedded LDAP to access it. The default user, weblogic, will also do just fine.

b) Find button action listener implementation looks like following:

    public void findButtonAL(ActionEvent actionEvent) {
        User user = (User)invokeEL("#{bindings.searchUser.execute}");
        
        if(user != null){
            setEL("#{pageFlowScope.displaySearchResult}", "true");
            setEL("#{pageFlowScope.user}", user);
            setUserRoles((List)invokeEL("#{bindings.getRolesForUser.execute}"));
        } else {
            setEL("#{pageFlowScope.displaySearchResult}", "false");
            setUserRoles(new ArrayList());
        }
    }


When you hit the find button, we are first invoking the searchUser method exposed by SecurityAM which searches the embedded LDAP for the user name. If the search is successful, the code further searches for all the roles defined for the searched user and displays them in the list view.

c) When a user is found following is the UI behaviour



You can verify the data from WLS console:



In above screenshot, you can see that the user weblogic has group membership (that is associated role) as “Administrators”.

d) When user is not found



What next?

You can extend this application and write your own code for doing all you want with user and roles in the embedded LDAP. This spans from creating, updating, deleting a user to managing the roles and memberships for particular users. In order to do that you may want to have a look at following javadocs:

1) UserManager class:
http://docs.oracle.com/cd/E12839_01/apirefs.1111/e14658/oracle/security/idm/UserManager.html

2) RolwManager class:
http://docs.oracle.com/cd/E12839_01/apirefs.1111/e14658/oracle/security/idm/RoleManager.html

Add all such APIs to IntegratedLDAPService and call them from AMImpl methods which UI layer can consume.

That’s it! You can download the complete application attached with the blog.

http://www.guardiansoftheoracleworld.com/blogs/adfman/2015/20151205_programmatic_integration_with_embedded_ldap.zip

Happy Coding!

Donnerstag, 8. Oktober 2015

Connecting to UCM through RIDC

Tech stack

Implementation demonstrated in this blog is developed using JDeveloper – version 11.1.1.7.0

Use case

In this little blog we will look at how can we connect to a UCM server and make service calls through RIDC programmatically.

Implementation steps

Step 1: If not already done, install Webcenter plugin to you JDeveloper IDE. This can be done simply using Help menu option “Check for Updates”. In the dialog that appears search for webcenter extension and install it. Restart JDev when prompted.


Step 2: Create a new Webcenter project by accepting defaults or open an existing one in which you want to programmatically access the UCM services. Now create a new UCM connection like below. This will need detail of your UCM server to which you desire to connect and use in your application.



In the dialog that appears you need to fill in details about your UCM server. In general, important attributes to be filled are:


  1. Connection Name: this can be anything you desire. 
  2. Repository Type: Oracle Content Server 
  3. Make sure you select the checkbox for “Set as primary connection for Documents service” 
  4. Next in Configuration Parameters provide following: 
    1. RIDC Socket Type: socket
    2. Server Host Name: this will be the host name of the UCM server machine. If UCM instance is running on local then it will be localhost. Do not write http:// or port details here. Just the plain host name will do just fine.
    3. Content Server Listener Port: 4444 (this is the default value for all UCM in general. If it is configured differently for your UCM then please provide the same)
    4. Web Server Context Root: /cs
    5. Test Connection: Test your connection to verify if its successful or not. In some cases you might also need to populate Admin Username and Admin Password fields in order to connect successfully.




Step 3: Now that we have setup a UCM connection we will see how to access it programmatically. Although the API to establish connection and fire a service call itself is pretty compact, it is always best to create a separate class which will implement all required helper APIs to cater to your project requirements (establishing connection using singleton approach, reading a file, updating a file metadata, upload an image/logo/document etc). This decision will depend upon your design so we will not dwell in that here.

Following is a simple class which establishes a connection and fires a standard UCM service request:

public class MyUCMService {
 static String ucmURL = "idc://dummy.com:4444";    
    private static DataBinder dataBinder;
    private static IdcClient idcClient;
    private IdcContext userContext; 
    private static String ucmAdminUser = "myadminuser";
    private static String ucmAdminPassword = "myadminpass";

    static {
            IdcClientManager manager = new IdcClientManager();
            try{
            idcClient = manager.createClient(ucmURL);
            dataBinder = idcClient.createBinder();
                           } catch (IdcClientException e) {
                                throw new ContentServiceException("Error in request IdcClient: " + e.getMessage(), e);
                           }
    }

    public MyUCMService() {
            userContext = new IdcContext(ucmAdminUser, ucmAdminPassword);
    }

    public InputStream getDocumentByName(String dDocName) throws Exception{
        if (dDocName == null)
            return null;
       
        try {
            dataBinder.putLocal("IdcService", "GET_FILE");
            dataBinder.putLocal("dDocName", dDocName);

            ServiceResponse response = idcClient.sendRequest(userContext, dataBinder);
            InputStream is = response.getResponseStream();
            return is;
        } catch (Exception e) {
            throw new Exception("Unable to get input stream for DocName "+dDocName+" Message: "e.getMessage(),e);
        }
    }
}


In above code snippet, we have demonstrated how we can establish a UCM connection and also how to invoke most commonly used GET_FILE service.

The constructor is responsible for initializing user context which will be followed by static block execution that prepares the DataBinder object. DataBinder object is responsible to set key request attributes – like what is the IdcService that you want to invoke (in our case GET_FILE), what all parameters/metadata the service need to use in order to prepare UCM query (in our case dDocName) etc.

Depending on the service, you can pass many parameters. Ultimately, sendRequest() method is used to fire the UCM request. On the UCM end it will decode the userContext and dataBinder objects to establish authorization and execute the desired operation.

Response is returned in the form of ServiceResponse object which can be used to read through the result set arriving as a result of query. This response object can be used to iterate through data objects or simply read the input stream (as we did). If you want to iterate through data objects returned then do something like below:

DataBinder responseDataBinder = response.getResponseAsBinder();
DataResultSet dataResultSet = responseDataBinder.getResultSet("SearchResults");
for (DataObject dataObject : dataResultSet.getRows()) {
 title = dataObject.get("dDocTitle");
}


Please note that there is a wide range of services that UCM provides in order to operate programmatically. You can refer following link to learn them: https://docs.oracle.com/cd/E23943_01/doc.1111/e11011/c04_core.htm#CSSRG2076

We hope you now have a fair knowledge of how to take advantage of UCM services programmatically and if you have any doubts/questions feel free to drop in your comments.

Until next time, Happy Coding !

Mittwoch, 26. August 2015

Using ExecuteWithParams action

Tech stack

Implementation demonstrated in this blog is developed using JDeveloper – version 11.1.1.7.0

Use case

In this blog we are going to see how we can use the often overlooked out-of-the-box action called ExecuteWithParams. This action basically lists all the bind variables associated with your view object and you can directly pass the values to these variables form the UI action binding to filter the VO. This saves you the time of writing the trivial AM method and calling it from a bean.

To illustrate the use of this action, we will create a query based view object which will have a bind variable in its query. On the UI, we will use single instance of this VO to show different data by sending different values for the bind variable.

Implementation steps

Step 1: Create EmployeeForDeptVO which will be our query based view object.



As you can see, the VO query has a bind variable pDeptId in the where clause. Expose this VO in the application module.



That’s pretty much our model side code. Let’s see how our UI is to be implemented.

Step 2: Create a jspx page with af:panelTabbed layout having 5 tabs, one each for departments: Administration, Meeting, Purchasing, Human Resources and Shipping. Upon running the page it will look like following:



Step 3: Now we will create a task flow which will look like following:



As can be seen above, the task flow has an input parameter deptId which the task flow consumer will pass. The default activity of this task flow is the ExecuteWithParams action which we drag and drop from data controls. Before adding the action to page, a dialog will ask us to provide values for all bind variables. In our case, the bind variable is pDeptId, for which we will provide #{pageFlowScope.deptId} as the value.



So what we did here is, we created a task flow which takes department Id as input parameter and pass this input value to ExecuteWithParams. Whenever this task flow is executed, the default activity ExecuteWithParams will trigger a VO query with input department Id and populate the rows accordingly.

The next activity in the task flow is a jsff page where we simply create a read only af:table using the EmployeeForDeptVO1 instance from data controls. This will load the rows of employees populated by the default activity.

Step 4: In the jspx page we created in step 3, add the above task flow inside each of the tabs (af:showDetailItem). Since there are 5 tabs, it will have 5 task flow bindings in the page definition file of the jspx page (one instance belonging to each tab).



Select each binding and pass the department Id as per the tab in which it belongs.



The department Id values will be: Administration = 10, Marketing = 20, Purchasing = 30, Human Resources = 40 and Shipping = 50.

We are now done with the UI with one small thing to do.

Step 5: If you run the page now, you will see that all the tabs displays same data. Any idea why this is happening?

This is happening because when the page is being loaded, all the tabs are trying to execute same task flow. But since we are using single VO, the tab for which query gets executed last decides what data will display in all the other tabs. In other words, if query for Administration tab is executed last then all the tabs will display employees for Administration department. How can we fix this?

The answer is to make the execution of task flow instances for each tab conditional. That is, we would want the task flow instance belonging to Marketing tab to get executed only when we click on Marketing tab. Similarly, for every individual tab, their respective task flow should get executed only when the particular tab is clicked.

In order to achieve this, we will do following:

Step 5.1 For each individual tab (af:showDetailItem), add an af:setPropertyListener as shown below:



As can be seen above, on tab disclosure event for Administration tab, we are setting property #{pageFlowScope.disclosedTab} as “administration”. Similarly set properties for all other tabs.


  
  
 
  




  
  
 
  




  
  
 
  




  
  
 
  



Step 5.2 In page definition select individual task flow instances and set the activation properties as shown below:



Above is task flow instance for Administration tab. Since on initial page load this tab is disclosed by default, we want its task flow instance to get executed. This is why we are adding to the activation condition #{pageFlowScope.disclosedTab == null} check. For other tabs the condition will be as follows.









Step 6: You are now ready to run and test the page :)



Alternative approach

Another approach to execute a view object with different values of query bind variables will be to expose an AM method and implement the UI side bean code to call it. In our approach we are doing the same without writing a single line of code and utilizing the ADF out of box action.

Summary

Hope you enjoyed this little blog. Until next time, live long, prosper and… Happy Coding!

Download the demo to take a look at the whole source code, which contains also some stuff, that is not explained here or just take a look at the video demonstration, in which the result is being presented.

Source Code: http://www.guardiansoftheoracleworld.com/blogs/adfman/2015/20150826_Using_execute_with_params.zip

Samstag, 1. August 2015

Heroic alternatives to avoid the annoying ADF SessionTimout Popups

Tech stack

Implementation demonstrated in this blog is developed/tested using JDeveloper version 11.1.1.9.0 and version 12.1.3

Use case

We all know the ADF popup, that pops up, after the Session timed out. I had quite a few customers, that were asking me, if they can change the text of the popup, forward to some specific page, after clicking OK or even automatically getting forwarded after the session timed out. All of them didn't really like the standard ADF popup, which also doesn't support accessibility.

Our goal in this blog is to get rid of this


and instead automatically forward to a nice looking page like this, after session timeout



Implementation steps

Step 1:  We set the session timeout to 1, while we are implementing this, so we don't have to wait 35 minutes for the session timeout. Don't forget to set this back to your old value, after you are done with the implementation.

 
1




Step 2:  At first we want to get rid of the ADF session timeout popup. We do this by making adjustments to the web.xml.


 
    javax.faces.STATE_SAVING_METHOD
    client
  
  
    oracle.adf.view.rich.sessionHandling.WARNING_BEFORE_TIMEOUT
    0
  
  

Step 3 : Now that we got rid of the default ADF popup, we want to create our own sessionTimeout.jspx file. This will be just a standard jspx file, that we will forward to after the session timed out.


  
  
    
      
        
      
    
  



Step 4 Now that we did find a solution on how to hide the popup and we also created our own custom sessionTimeout.jspx file there is one big question. How do we forward to this page after the session timed out??? I was looking around in the world wide web and found a really nice blog, which had a great solution.

http://www.javaworld.com/article/2073234/tracking-session-expiration-in-browser.html

So our task in step 4 is reading the blog to understand how the forward is working. If you are feeling like an expert, you can skip reading the blog :-)

In short I can tell you this. A servlet filter is attaching to each response a cookie with a timestamp, telling when the session will expire. On Javascript side we read this information each second from the cookie and calculate, if the session already timed out. If the session timed out, we will forward to the sessionTimeout.jspx. So let's begin.

Step 4.1 Create your own custom servlet filter, which is reading the sessionExpirationTime and the serverTime and attaching it to the httpResponse as cookie.
public class SessionTimeoutFilter implements Filter {
  public SessionTimeoutFilter() {
    super();
  }

  public void init(FilterConfig filterConfig) {
  }

    /**
     * Custom filter that is writing the current serverTime and the sessionExpiry time to a cookie
     * The current time is needed to calculate the offset between server and client time
     * 
     * @param req
     * @param resp
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {
    HttpServletResponse httpResp = (HttpServletResponse)resp;
    HttpServletRequest httpReq = (HttpServletRequest)req;
    long currTime = System.currentTimeMillis();
    long expiryTime = currTime + httpReq.getSession().getMaxInactiveInterval() * 1000;
    Cookie cookie = new Cookie("serverTime", "" + currTime);
    cookie.setPath("/");
    httpResp.addCookie(cookie);
    cookie = new Cookie("sessionExpiry", "" + expiryTime);
    cookie.setPath("/");
    httpResp.addCookie(cookie);
    filterChain.doFilter(req, resp);
  }

  public void destroy() {
  }
}


Step 4.2 In the web.xml we have to register our custom filter.
  
    SessionTimeoutFilter
    com.guardiansoftheoracleworld.adfman.blog.view.SessionTimeoutFilter
  
  
    SessionTimeoutFilter
    /*
  

Step 4.3 Create a Javascript function, which is reading the sessionExpiration each second and checking, if the session already timed out. If the session timed out, a forward will be made to our custom sessionTimeout.jspx file. The function calling itself is made recursevly with the setTimeout function.
This Javascript code has also some not explained advanced features, like a counter or a message telling, that the session timed out and a forward will be made in five seconds.
/**
 * Check periodically each second, if the session has timed out
 */
function checkSession() {
  var outputText = AdfPage.PAGE.findComponentByAbsoluteId("ot3");
    
  var sessionExpiry = Math.abs(getCookie('sessionExpiry'));
  var timeOffset = Math.abs(getCookie('clientTimeOffset'));
  var localTime = (new Date()).getTime();
  if (localTime - timeOffset > (sessionExpiry + 5000)) {
    // 5 extra seconds to make sure
    window.location = "/TimeoutSessionFilter-ViewController-context-root/faces/sessionTimeout.jspx";
  } else if (localTime - timeOffset > sessionExpiry) {
    outputText.setValue('Session timed out. Forward in five seconds !!!');  
    setTimeout('checkSession()', 1000);
  }
  else {
    // calculate remaining time in seconds to show it to the client
    var remainingTime = Math.round((sessionExpiry - (localTime - timeOffset)) / 1000);
    outputText.setValue('remainingTime: ' + remainingTime + ', timeOffset: ' + timeOffset);
    setTimeout('checkSession()', 1000);
  }
}

Step 4.4 The last step is, that we have to call the checkSession() function once to trigger this whole thing. We can do this with an ADF clientListener.
      


Summary

This is a quite good alternative to the ADF popup and should also be compatible with future ADF versions, since it hasn't too much to do with ADF itself.
Download the demo to take a look at the whole source code, which contains also some stuff, that is not explained here or just take a look at the video demonstration, in which the result is being presented.

Video Demo: https://www.youtube.com/watch?v=6GA49ONnG3o

Source Code: http://www.guardiansoftheoracleworld.com/blogs/adfman/2015/20150801_session_timeout_forward.zip

Freitag, 24. Juli 2015

Bulk Delete using Check Box in af:table

Tech stack

Implementation demonstrated in this blog is developed using JDeveloper – version 11.1.1.7.0


Use case

Often times in a project there are requirements to implement a bulk delete functionality. Most of them are expected to be implemented on af:table which will show several rows. There are more than one way this can be achieved. The most common user friendly way is to display check boxes for each row so that the user can simply select the rows that he/she want to delete and finally click on a button to delete them.

In this blog we will look at this approach and how this can be achieved.




Implementation steps

Step 1: We will begin with creating a transient attribute of Boolean type on the VO which is used to show table on the UI.




As you can see we have also changed the “Control Type” attribute in “Control Hints” to Check Box. This will declaratively make this attribute behave like a check box on all layers.


Step 2: On the page fragment where the af:table is present, add the attribute as the first column.



Here, note that we have also set the rowSelection property of af:table to none. This is very important change because we want to programmatically read and handle the VO iterator changes. Also, we have created a table binding which will be used in next step.


Step 3: Create and expose an AM client method which will take a List object as an input. This input parameter will be passed from the UI and will carry all the selected row keys to be deleted.



As shown above, the bulkDeleteEmployees method is created in AMImpl and below exposed as a client method.



This method implements a for-each loop and goes over each element present in the input parameter list. For each element it find the row and removes the same from VO. Finally, a DB commit is performed.


Step 4: Add a button which will be used to delete the selected rows. Partial submit must be enabled on it. Create an action listener method for this button in the managed bean.


The action listener will look like following


The action listener, deleteSelectedEmployees, shown above is doing following:
  1. Taking the handle of the VO iterator which is used to create the table.
  2. Iterating over all the rows present and checking for IsSelected attribute for each row.
  3. If the attribute is returning true it means the row is selected. Adding the key of selected row to a list.
  4. Once all rows are processed, the method is calling the AM method, bulkDeleteEmployees, by passing the list of all selected row keys. The AM method, described in previous step, will take care of deleting the rows and committing changes.
  5. Once done the VO iterator is refreshed and the table binding is triggered to reflect the changes.

Another approach

The same scenario can also be handled without using the check box approach. That will require you to change rowSelection property to multiple on af:table and then programmatically read the selected row keys to delete them. The only drawback with that approach is that it is less recommended UI behaviour and most of the clients go for check box approach for better UI experience.

That’s it! You can download the complete application attached with the blog.
http://www.guardiansoftheoracleworld.com/blogs/adfman/2015/20150723_bulk_delete_using_checkbox_in_table.zip

NOTE: The demo application is based on HR schema. The table I have used is based on the Employees table. If you look at the VO query, I have made a tweak to bring only those employee records who are not managers. If you try to delete a record whose is a manager it will give you foreign key exception and we don’t want to complicate our simple demo by handling that ;)

Happy Coding!

Donnerstag, 25. Juni 2015

Refreshing UI component in Parent TF from nested Child TF

Consider a scenario in which you want to refresh some component present in consuming task flow (parent task flow) upon some action in consumed task flow (child task flow).

To elaborate, you have a task flow and its page fragment (lets say ParentTF.xml and parentPF.jsff). Inside parentPF.jsff there are two components:

  1. An output text component
  2. Another task flow in the form of a region with its own page fragment (lets call it ChildTF.xml and childPF.jsff). There is a command button titled “Refresh” inside childPF.jsff fragment.

Now the requirement is such that when you hit “Refresh” button in child fragment, you want to immediately update the output text value on the parent fragment.

It is a very simple scenario but it can be complicated if there are many levels of child task flows included one inside another. Having multiple parent activity call for all nested task flows is cumbersome. Also, using a complex approach like contextual events for such a simple use case may probably be the last thing in the world you would like to try out.

In this blog we will look at a simple approach of how this can be achieved.

Step 1: Create a bean which will have binding for the output text present on parentPF.jsff and a method to partial trigger the output text. Important thing to note is that this bean is created for this particular special use case and will be registered with ParentTF in request scope. All other UI side logic should be written in separate managed bean.





As you can see there is output text binding added to the bean alongwith a static method refreshParentOutputText().

The beauty here is, anywhere in the application this static method can be called which will refresh the output text, provided the bean is initialized.

Step 2: In the managed bean of ChildTF call the refreshParentOutputText() upon “Refresh” button click. As you can see we are first capturing the input text value in a view scope variable and on “Refresh” button click we are passing the same to refreshParentOutputText() method.




When we run the Index.jspx we will see following:





In above screenshot, the output text component “Refreshed value:” belongs in ParentTF which has ChildTF as a region. Inside ChildTF we have the input text component and Refresh button.
Enter some value in the input text and hit Refresh to see the desired result:




That’s it! You can download the complete application attached with the blog.

Happy Coding!