How To Support Dynamic JDBC Credentials

Written by Avrom Roy-Faderman, Michael Gantman, and John Smiljanic, Oracle Corporation June 2004

Introduction

If you have already worked with ADF Business Components, you may have noticed that declaratively created ADF applications assume that all application sessions connect to the database as the same database user.  This is a typical assumption that is popular for many applications that use shared authentication frameworks, like an LDAP based single sign-on server, and virtual private databases to maintain audit information.  However, many enterprise database applications also exist that leverage a database user to provide application authentication or to maintain audit information.  This database user and its associated password are typically specified by an application user when that user first logs into an application.  This document will describe how to extend the ADF runtime framework to support dynamic database users in a single virtual machine.

 

Content

 

 

Overview

Content

In order to support dynamic JDBC credentials the application developer must pass those JDBC credentials from the user to ADF Business Components.  Once passed a session's JDBC credentials, ADF Business Components will transparently guarantee that the session's application module is connected to the database with the specified credentials.  So, the good news is that most of ADF Business Components' dynamic JDBC credential support has already been implemented.  The focus of this article will be on overriding the ADF Binding Filter to allow the user to set dynamic JDBC credentials on the session context, implementing a dynamic JDBC credential provider, specifying that provider to the ADF Business Components runtime.  Before we jump into the code, the paragraphs below provide a brief overview of the ADF application module and connection pooling frameworks.  This overview should help you better understand how your dynamic JDBC credential provider will interact with the ADF Business Components runtime.

If you have developed an ADF web application with ADF Business Components you may have noticed that the database connection which was used by an HTTP request remains open after that request has finished.  This happens because the ADF Business Components runtime uses a common software development practice, known as pooling, to reuse database connections and application modules between requests.  The reuse of application modules and connections between requests eliminates the time that must be spent opening database connections and creating application module instances for each request.

Since an application module pool, known informally as an application pool, is responsible for managing application module instances between requests, it is also responsible for the life cycle of those application modules -- the application pool must create an application module instance when no instances are available and it must remove/reuse application module instances when critical resources are exhausted.  Since the application pool is responsible for an application module life cycle it is also responsible for connecting/disconnecting an application module.  So, when a request requires an application module from an application pool it can expect that application module instance to be in a connected, valid state.

The JDBC credentials that the application pool will use to connect an application module to the database are usually stored in the application module's configuration.  These credentials are consequently shared by all sessions which are using that pool.  This design works well if the application pool is supporting many sessions which require only one JDBC user.  However, what happens if the application requires that an application pool instance support many JDBC users?  Further, how do you pass the credentials for each of those JDBC users to the application pool?

The session management features of the application pool can detect when an application module that was last used by a request from session X representing database user A has been reused by a request from session Y possibly representing database user B.  In this scenario, the pool will disconnect the existing application module instance and attempt to reconnect as database user B.  However, the pool must be provided the dynamic database credentials for sessions X and Y in order to establish the connections.  This article will illustrate how to implement a dynamic JDBC credential provider.  This provider will be invoked by the application pool when it detects that an application module instance must be disconnected and reconnected as described above.

 

Setting Up the Application

Content

The techniques described in this article allow you to transform an application that uses standard (static) JDBC credentials into an application that uses dynamic JDBC credentials. This section briefly covers setting up a simple ADF application that uses static JDBC credentials as a starting place for the rest of the article.

Create the application

This article covers ADF applications that use model-2 web clients. The following steps describe how to create such an application:

  1. Create a connection to the database, using static credentials.
  2. In the Applications Navigator, on the Applications node, choose New Application Workspace from the context menu.
  3. Create your application using the Web Application [default] template.
  4. In the Model project, create ADF Business Components using your database connection.
  5. In the ViewController project, create a Struts page flow and pages. Be sure to include a login page to allow people to enter their information.
  6. Add databound controls to at least one page, other than the login page (of course, you should not attempt to add databound controls to the login page). This can be done using the Data Control Palette; for more information, see the JDeveloper online help.
The example

In this example, we will describe a very simple page flow, which will be used in the remaining examples in this paper. The page flow contains two datapages: /login and /main. /login forwards to a JSP page called login.jsp, that allows users to enter their login information. /main forwards to the main page of the application, which contains some databound controls.

Design the login page

The login page you create must have two features:

  • It must accept JDBC credentials in a form.
  • In order to distinguish the login request from all other requests the login form must be uniquely identifiable by attributes of its HTTP request. For example, the form is the only POST request for the main.do action..
The example

In this example, we will show how to implement a login page that meets these requirements.

The page's source is as follows:

<%@ taglib uri="#" prefix="c"%>
<%@ page contentType="text/html;charset=windows-1252"%>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
          <title>Login Page</title>
        </head>
        <body>
    <%
                              

      if (request.getAttribute("failed") != null)                              <!-- line 1 -->
                              

      {
                              

    %>
                              

        <b>Login Failed. Please Try Again.</b>
                              

    <%
                              

      }
                              

    %>
    <form method="post" action="main.do" name="LoginForm">                     <!-- line 2 -->
            <P>
              Username:
                  <input name="username" size="15" maxlength="100"/></span>              <!-- line 3 -->
            </P>
            <P>
              Password: 
                  <input type="password" name="password" size="15" maxlength="100"/>     <!-- line 4 -->
      </P>
      <P>
                  <input type="submit" value="login"/>
            </P>
          </form>
  </body>
</html>
                            

Line 1 checks for the presence of a request attribute called "failed" to see if the user has already attempted to log in. This attribute will be set in the ADF binding filter upon login failure. If the attribute is found, the page prints out an error message requesting another login attempt.

Line 2 submits the form to the other data page, /main. Note that the form uses the POST method--this allows the ADF binding filter to uniquely identify requests submitted from this form.

Lines 3-4 accept credentials and pass them as parameters called "username" and "password".

 

 

Overriding the ADF Binding Filter

Content

The ADF Binding Filter is a servlet filter that sets up the binding context for the ADF application. It intercepts HTTP requests and, among other tasks, requests an application module instance from the application pool.

Because of this, you cannot use the ADF Binding Filter directly if you are going to use dynamic JDBC credentials. A user must be able to enter these credentials on a page, but before any page is displayed, the ADF Binding Filter will fire and request an application module instance. Since no JDBC credentials have yet been specified, the instance will be unable to connect and the operation will fail. To avoid this, you must override the ADF Binding Filter to allow a user to access the login page before JDBC credentials have been set.

Design it

You should override the ADF Binding Filter in your ViewController project. Overriding the filter requires that the "BC4J Client" library be on the classpath, so edit the ViewController project's project properties and add the "BC4J Client" library.

To override the ADF Binding Filter, you are going to extend the class oracle.adf.model.servlet.ADFBindingFilter, and override the doFilter() method. The method must do the following:

  • Identify whether the user is already logged in.
  • If the user is logged in, call the superclass' doFilter() method to retrieve an application module.
  • Otherwise, test to see if the request came from the login page.
  • If not, display the login page.
  • If so, add the username and password to the HTTP session context, and call the superclass' doFilter() method to attempt to retrieve an application module.
  • If the attempt fails, forward the user back to the login page so that they can attempt to log in again.
The Example
  • The login page is displayed by a data page called /login
  • The login form forwards the username (as the request parameter "username") and password (as the request parameter "password") to a data page called /main, using the POST method, and is the only form in the application that forwards to /main using the POST method.
  • The login page checks a request parameter, "failed", and displays a "Login failed" message if it is not null.

The code of the ADF Binding Filter subclass, under these conditions, is as follows:

package view;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import oracle.adf.model.servlet.ADFBindingFilter;
import oracle.jbo.JboException;
import oracle.jbo.client.Configuration;


public class DynamicJDBCBindingFilter extends ADFBindingFilter 
{
  public DynamicJDBCBindingFilter()
  {
  }
   public void doFilter(
      ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException
  {
    HttpSession session = ((HttpServletRequest)request).getSession(true);

    if(session.getAttribute("loggedin")==null)                                  // line 1
    {

      if(!(((HttpServletRequest)request).getRequestURL().indexOf("main.do")!=-1 && 
           ((HttpServletRequest)request).getMethod().equalsIgnoreCase("POST"))) // line 2
      {
        
        request.getRequestDispatcher("/login.do").forward(request, response);
      }
      else 
      {
        try 
        {

          String usrName = request.getParameter("username");
          String pswd = request.getParameter("password");

          if(usrName == null || usrName.length() == 0 ||                        // line 3
             pswd == null || pswd.length() == 0) 
          {
            throw new JboException("Blank User name or Password");
          }

          session.setAttribute(Configuration.DB_USERNAME_PROPERTY, usrName);    // line 4
          session.setAttribute(Configuration.DB_PASSWORD_PROPERTY, pswd); 

          super.doFilter(request, response,chain);                              // line 5
                  
          session.setAttribute("loggedin", "loggedin");                         // line 6
        }
        catch (oracle.jbo.JboException e)                                       // line 7
        {
          try
          { 
            session.setAttribute("loggedin", null);
            request.setAttribute("failed", "failed");                           // line 8
            session.invalidate();
          } 
          catch (Exception e2)                                                  // line 9
          { 
          }
          request.getRequestDispatcher("/login.do").forward(request, response); // line 10
        } 
      }
    }
    else 
    {
      super.doFilter(request, response,chain);                                  // line 11
    }
  }
}
       

Line 1 checks a session context attribute called "loggedin" to see if the user is already logged in. If the user is logged in, the method goes directly to line 11 to execute the superclass' doFilter(). Otherwise, the method will attempt to log the user in. If it is successful, the "loggedin" attribute will be set on line 6.

Line 2 is intended to check whether the user has visited the login page. Because of line 1, the user has not been logged in, so there are two possibilities: Either they have just submitted their login information (in which case, the current HTTP request will be a request for main.do using the POST method) or they have not yet submitted their login information. If they have not just submitted their login information, the method forwards them to the login page; otherwise, the method continues executing in the block's else segment.

Line 3 checks to make sure the user has indeed entered both a username and a password. If either is blank, the JDBC credentials are guaranteed to be inaccurate, so the method immediately throws an exception (which is caught on line 7).

Line 4 stores the credentials the user has entered in the HTTP session context. This example uses the session properties Configuration.DB_USERNAME_PROPERTY and Configuration.DB_PASSWORD_PROPERTY to store the JDBC user name and password for the configuration. You can store the information in another way if you prefer.

Line 5 calls the superclass' doFilter() and attempts to retrieve an application module instance with the JDBC credentials set in line 4. If there are any problems (for example, if authentication fails), the exceptions will be caught on line 7.

Line 6 sets the "loggedin" attribute checked on line 1. If the authentication is successful, the code in this subclass should be bypassed on future requests. Note that the name, "loggedin", is used as an example only; all that is important is that it match the name of the attribute checked on line 1.

Line 7 catches problems with the login process--either the exception thrown on line 3 or exceptions thrown by the superclass' doFilter() method called on line 5. If login failed, the session should be invalidated.

Line 8 creates a request parameter, "failed", that will be forwarded to the login page on line 10. You can write your login page to check this parameter and display an error message on second and further attempts to log in. Note that the name, "failed", is used as an example only; all that is important is that it match the parameter name checked in the login page. The value is not important at all in this example; the login page simply checks whether the attribute is null.

Line 9 catches any further exceptions.

Line 10 forwards back to the login page so the user can reattempt to log in.

Line 11 is only executed if the test in line 1 fails--that is, if the user is already logged in. It bypasses the code in this extended method and directly calls the superclass' doFilter() method.

Register it

After you have created your subclass of the ADF Binding Filter, you must alter your ViewController project's web.xml file so that your application will use your subclass instead of oracle.adf.model.servlet.ADFBindingFilter. To do this, find the <filter> element that contains a <filter-name> subelement of ADFBindingFilter, and change its <filter-class> subelement to point to your subclass.

The Example

In this example, we will show how to change the <filter> element in the web.xml file. This example has the following presuppositions:

  • You have named the ADF Binding Filter subclass DynamicJDBCBindingFilter and put it in the view package.
  • You are using the windows-1252 encoding.

Look for the following element in the web.xml file:

  <filter>
    <filter-name>ADFBindingFilter</filter-name>
    <filter-class>oracle.adf.model.servlet.ADFBindingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>windows-1252</param-value>
    </init-param>
  </filter>

Alter this element to the following:

  <filter>
    <filter-name>ADFBindingFilter</filter-name>
    <filter-class>view.DynamicJDBCBindingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>windows-1252</param-value>
    </init-param>
  </filter>

 

 

Setting ADF Business Components' Internal Connection Property

Content

At run time, the ADF Business Components runtime connects to the database to create and update data in its utility tables. By default it creates these tables in the current user's account; in this case, however, the current user can vary and might not always have the correct privileges, so you need to make ADF Business Components connect to a predictable account with create table and create sequence privileges. The jbo.server.internal_connection property defines the connection that BC4J uses to access its utility tables.

The following steps describe how to set the jbo.server.internal_connection property:

  1. In the Application Navigator, in your Model project, right-click your application module and select "Configurations..."
  2. In the Configuration Manger, select the configuration your application module will use at runtime ( YourAMNameLocal by default), and click Edit.
  3. In the Configuration editor, select the Properties tab.
  4. Find the jbo.server.internal_connection property and type a new value in the Value column. The new value can be a connection string (for example, jdbc:oracle:thin:bc4j/bc4j@localhost:1521:ORCL) or the name of a J2EE datasource created for this purpose (for example, MyJ2EEDataSourceName).
  5. Click OK in the Configuration editor, then click OK in the Configuration Manager.
jbo.server.internal_connection
  1. Right-click your application module and select "Test..."
  2. In the Connect dialog, select the YourAMNameLocal configuration from the poplist at the upper-right.
  3. Click Connect.
  4. In the Business Component Browser, make a modification to a row of data.
  5. Select File | Save Transaction State (ADF Business Components uses its internal connection to save transaction state) and note the transaction id.
  6. Using SQL*Plus or JDeveloper's database browser, verify that BC4J has passivated your transaction state using the internal connection you specified, and not the connection specified in YourAMNameLocal. The table PS_TXN should exist, and should contain a new row with the correct transaction id (check the COLLID column) and creation date.

 

Creating a Dynamic JDBC Credential Provider

Content

ADF Business Components declares the oracle.jbo.common.ampool.EnvInfoProvider interface to provide dynamic JDBC credentials to an application pool.  This interface will be invoked by the ADF Business Components runtime when it is necessary to connect an application module to the database.

Design it

The following steps illustrate how to implement the EnvInfoProvider interface in Oracle JDeveloper 10g:

  1. Select your Model project and choose New... from the right mouse menu.
  2. In the New Gallery, select Java Class from the General category.
  3. Type a name for the EnvInfoProvider in the Name: field.
  4. Deselect the Generate Default Constructor checkbox, and click OK.
  5. Select the new class in the JDeveloper navigator and then select the menu option, Tools | Implement Interface...
  6. Use the package browser to find and select the oracle.jbo.common.ampool.EnvInfoProvider interface, and click OK.

Notice that the JDeveloper IDE should have created a "stub" implementation of the oracle.jbo.common.ampool.EnvInfoProvider interface.  The stub implementation includes valid, empty implementations for each of the operations that the EnvInfoProvider interface declares.

The operations declared by the EnvInfoProvider interface are all invoked at different points in the application module creation/connection life cycle by the ADF Business Components runtime:

  • getInfo(String, Object) is invoked prior to connecting an application module instance.  It provides an EnvInfoProvider implementation with a hook to dynamically specify the JDBC credentials that will be used to establish the connection.
  • modifyInitialContext(Object) is invoked prior to instantiating a new application module instance.  It provides an EnvInfoProvider implementation with a hook to dynamically add/update the application module's initial context.
  • getNumOfRetries() is also invoked while connecting an application module instance.  It provides an EnvInfoProvider implementation with a hook to dynamically specify the number of times the ADF Business Components runtime should attempt to establish a JDBC connection after a failed attempt.
The Example

In this example, we will show how to implement the EnvInfoProvider interface. The example will implement the getInfo() method, but will accept the stub implementations of the modifyInitialContext() and getNumOfRetries() methods, since dynamic initial context and failed connection tolerance are outside the scope of this article.

package model;
import java.util.Hashtable;
import oracle.jbo.common.ampool.EnvInfoProvider;
import oracle.jbo.client.Configuration; 


public class DynamicJDBCEnvInfoProvider implements EnvInfoProvider 
{
  private final String mJDBCUserName; 
  private final String mJDBCPassword; 


  public DynamicJDBCEnvInfoProvider(String jdbcUserName, String jdbcPassword)                // line 1
  { 
     mJDBCUserName = jdbcUserName; 
     mJDBCPassword = jdbcPassword; 
  } 


  public Object getInfo(String info, Object connEnvironment)
  {
    if(mJDBCUserName != null)                                                                // line 2
    {
      ((Hashtable)connEnvironment).put(Configuration.DB_USERNAME_PROPERTY, mJDBCUserName);   // line 3
    }
    if(mJDBCPassword != null)                                                                // line 4
    {
      ((Hashtable)connEnvironment).put(Configuration.DB_PASSWORD_PROPERTY, mJDBCPassword);   // line 5
    }

    return null;                                                                             // line 6
  }


  public void modifyInitialContext(Object p0)
  {
  }


  public int getNumOfRetries()
  {
    return 0;
  }
}
       

Line 1 declares a constructor for the EnvInfoProvider.  There is only one constructor provided for this class.  The constructor requires that the constructing method specify a jdbcUserName and a jdbcPassword when creating the EnvInfoProvider instance.  The references to the JDBC credentials are then stored as private member variables of the EnvInfoProvider instance.  Note that the credentials are also immutable once the EnvInfoProvider has been instantiated.

Line 2 of the implementation checks to see if the EnvInfoProvider() constructor was provided with a username. The ADF BC runtime may not instantiate the EnvInfoProvider with a username if if the DB_USERNAME_PROPERTY property has already been specified in the application module configuration.

L ine 3--which only executes if the ADF Business Components runtime is using the EnvInfoProvider to supply the username--adds the username to the connection environment.  mJDBCUserName is a private final instance variable of the MyEnvInfoProvider instance.  You will see how the mJDBCUserName is specified in the next section.

Line 4 of the implementation checks to see if the EnvInfoProvider() constructor was provided with a password. The ADF BC runtime may not instantiate the EnvInfoProvider with a password if the DB_PASSWORD_PROPERTY property has already been specified in the application module configuration.

L ine 5--which only executes if the ADF Business Components runtime is using the EnvInfoProvider to supply the password--adds the password to the connection environment.  mJDBCPassword is a private final instance variable of the MyEnvInfoProvider instance.  You will see how the mJDBCPassword is specified in the next section.

Finally, Line 6 returns from the method. Nothing needs to be returned, as the changes to connEnvironment were made in the body of the method.

 

Referencing the Dynamic JDBC Credential Provider at Runtime

Content

The application pool uses oracle.jbo.common.ampool.SessionCookie(s) to uniquely identify application pool sessions. Every HTTP request that requires an application module instance will create a SessionCookie. Once created, the SessionCookie is cached in the HttpSession context for reuse throughout the lifetime of the session.  SessionCookie instances are created automatically by the Oracle ADF model layer whenever a new HttpSession requires an application module instance.  The SessionCookie instance is then used by the model layer to acquire an application module instance for the session's request.

Because of the one-to-many relationship between an HttpSession and the SessionCookie, the SessionCookie is a logical choice for storing an object like the EnvInfoProvider that has session scope and must be accessed by the ADF Business Components runtime.  The ADF Business Components API provides the SessionCookie.setEnvInfoProvider(EnvInfoProvider) method for storing a reference to an EnvInfoProvider instance on a SessionCookie.  The application pool will check for the existence of an EnvInfoProvider when it determines that an application module instance must be connected.  If the session which has required an application module has defined an EnvInfoProvider, and if the application pool has not already been configured with a static set of JDBC credentials, then the EnvInfoProvider will be invoked to retrieve the JDBC credentials.  But, how do you set the SessionCookie EnvInfoProvider if the ADF model layer is creating that cookie and acquiring an application module instance for you?

To solve this problem, ADF Business Components allows an application developer to extend the framework and to provide a custom implementation for the way in which SessionCookie(s) are created.  Whenever the ADF model layer requires a new SessionCookie instance, it uses a factory to create that instance.  Factories are a common object-oriented design pattern for defining a pluggable object creation service.  ADF Business Components allows the application developer to declare which factory to use to create a SessionCookie.

Before you create a custom session cookie factory, you must also create a custom SessionCookie implementation. This is a workaround for a known bug in ADF which causes web clients to ignore custom EnvInfoProviders. This bug is scheduled to be fixed in a future release of JDeveloper; when it is, you will be able to skip the implementation of SessionCookie and proceed directly to creating the session cookie factory.

Create a custom SessionCookie implementation

You should create your SessionCookie implementation in your Model project. Implementing the interface requires that the "ADF Web Runtime" and "Servlet Runtime" libraries be on the classpath, so edit the Model project's project properties and add the "ADF Web Runtime" and "Servlet Runtime" libraries.

To create your SessionCookie implementation, you will extend oracle.jbo.http.HttpSessionCookieImpl (the framework's implementation of SessionCookie for web clients). Your implementation will fix the bug which causes web clients to nullify the EnvInfoProvider property of the SessionCookie, causing the application to ignore custom EnvInfoProviders. Note that you can skip this step if you are using a version of JDeveloper in which the bug is fixed.

The Example

In this example, we will show how to extend oracle.jbo.http.HttpSessionCookieImpl to create a custom SessionCookie implementation.

package model;
import oracle.jbo.common.ampool.ApplicationPool;
import oracle.jbo.http.HttpSessionCookieImpl;
import oracle.jbo.common.ampool.EnvInfoProvider;


public class DynamicJDBCHttpSessionCookieImpl extends HttpSessionCookieImpl 
{


  public DynamicJDBCHttpSessionCookieImpl(java.lang.String applicationId,         // line 1
                               java.lang.String sessionId,
                               ApplicationPool pool)
  {
    super(applicationId, sessionId, pool);
  }

  public DynamicJDBCHttpSessionCookieImpl(java.lang.String applicationId,         // line 2
                               java.lang.String sessionId,
                               ApplicationPool pool,
                               java.security.Principal userPrincipal,
                               javax.servlet.http.HttpServletRequest request) 
  {
    super(applicationId, sessionId, pool, userPrincipal, request);
  }
  
  public DynamicJDBCHttpSessionCookieImpl(java.lang.String applicationId,          // line 3
                               java.lang.String sessionId,
                               ApplicationPool pool,
                               java.security.Principal userPrincipal,
                               javax.servlet.http.HttpSession session)  
  {
    super(applicationId, sessionId, pool, userPrincipal, session);
  }
                             
  public void setEnvInfoProvider(EnvInfoProvider envInfoProvider)
  {
    if(envInfoProvider!=null)
    {
      super.setEnvInfoProvider(envInfoProvider);
    }
  }
} 

Lines 1-3 of the implementation declare constructors. These constructors simply invoke the superclass' constructor--they are necessary to provide a consistent API with the parent class.

Line 4 of the implementation overrides the setEnvInfoProvider() method to ignore attempts to nullify the EnvInfoProvider property. This prevents web clients from nullifying the EnvInfoProvider property and ignoring your custom EnvInfoProvider.

Create the SessionCookie factory

You should create your SessionCookie factory in your Model project. Implementing the interface requires that the "ADF Web Runtime" and "Servlet Runtime" libraries be on the classpath, so edit the Model project's project properties and ensure that those libraries are there.

To create your SessionCookie factory, you will extend oracle.jbo.http. HttpSessionCookieFactory (the framework's default implementation of SessionCookie). Your factory will initialize new SessionCookie(s) with instances of your EnvInfoProvider class.

The Example

 

In this example, we will show how to extend oracle.jbo.http.HttpSessionCookieFactory to create a custom SessionCookie factory . This example has the following presuppositions:

  • Your custom EnvInfoProvider is called DynamicJDBCProvider (as in the example in this article).
  • Your extension of the ADF Binding Filter uses session properties called Configuration.DB_USERNAME_PROPERTY and Configuration.DB_PASSWORD_PROPERTY (as in the example in this article) to store the username and password information.
  • Your custom SessionCookieImplementation is called DynamicJDBCSessionCookieImpl (as in the example in this article).
package model;
import oracle.jbo.common.ampool.SessionCookie; 
import oracle.jbo.common.ampool.ApplicationPool; 
import oracle.jbo.common.ampool.SessionCookieFactory; 
import oracle.jbo.common.ampool.ConnectionStrategy; 
import oracle.jbo.common.ampool.EnvInfoProvider; 
import java.util.Properties; 
import java.util.Hashtable; 
import oracle.jbo.http.HttpSessionCookieFactory; 
import oracle.jbo.client.Configuration; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpSession; 


public class DynamicJDBCSessionCookieFactory extends HttpSessionCookieFactory 
{ 


  public SessionCookie createSessionCookie(String name, String value, ApplicationPool pool, 
     Properties properties) 
  { 
     SessionCookie cookie = super.createSessionCookie(name, value, pool, properties);       // line 1 

     if (properties != null)
     { 
        HttpSession session = (HttpSession)properties.get(HTTP_SESSION);                    // line 2
        if(session != null)
        { 
           Hashtable env = pool.getEnvironment();                                           // line 3 
           env.remove(Configuration.DB_USERNAME_PROPERTY); 
           env.remove(Configuration.DB_PASSWORD_PROPERTY); 
           pool.setUserName(null); 
           pool.setPassword(null); 
           EnvInfoProvider provider = new DynamicJDBCEnvInfoProvider(                       // line 4 
                (String)session.getAttribute(Configuration.DB_USERNAME_PROPERTY), 
                (String)session.getAttribute(Configuration.DB_PASSWORD_PROPERTY)); 
           cookie.setEnvInfoProvider(provider);                                             // line 5 
        } 
     } 


     return cookie; 
  } 

  public Class getSessionCookieClass()                                                      // line 6
  {

    try {
      return Class.forName("model.DynamicJDBCHttpSessionCookieImpl");
    }
    catch (ClassNotFoundException cnfe) 
    {
      cnfe.printStackTrace();
      return null;
    }
  }
} 

Line 1 of the implementation uses the the parent class to create a SessionCookie instance.

Line 2 of the implementation retrieves the current HTTP session, which is passed as part of the properties parameter.

Lines 3, and the next four lines, reset any potential JDBC credentials which may already be defined.  These steps are a necessary precaution to prevent the application pool from connecting using JDBC credentials which may have been declared in a separate HTTP session.  If the credentials have been declared in the application module configuration then the pool will not need to use the EnvInfoProvider to store those credentials.

Line 4 instantiates a new instance of your EnvInfoProvider class.  Note that this presupposes that the ADF Binding filter has stored the username and password in session properties called Configuration.DB_USERNAME_PROPERTY and Configuration.DB_PASSWORD_PROPERTY, respectively.

Line 5 sets the SessionCookie with the new instance of your EnvInfoProvider.

Line 6 declares a method that overrides the HTTPSessionCookie.getSessionCookieClass() method to use your custom SessionCookie implementaion as a workaround for the nullified EnvInfoProvider bug. Note that, when this bug is fixed, you will not need to override this method.

Register it

Now that you have defined your SessionCookieFactory you must force the application pool to use that factory instead of the default factory.  This may be performed declaratively with the jbo.ampool.SessionCookieFactory property.

The example

In this example you'll use the application module configuration to set the property:

  1. Select an existing ADF application module and choose Configurations... from the right mouse menu.
  2. Select the named configuration that will be used by this application and select the Edit button.
  3. Select the Properties tab page.
  4. Locate the jbo.ampool.sessioncookiefactoryclass property in the Properties table.  Replace the default value of the property, oracle.jbo.common.ampool.DefaultSessionCookieFactory, with the fully qualified name of your SessionCookie factory, such as model.DynamicJDBCSessionCookieFactory.
  5. Select OK from the Configuration editor and select OK from the Configuration manager.

 

false ,,,,,,,,,,,,,,,