Wednesday Jun 26, 2013

Adaptive Connections For ADFBC

Some time ago I wrote an article on Adaptive Bindings showing how the pageDef for a an ADF UI does not have to be wedded to a fixed data control or collection / View Object. This article has proved pretty popular, so as a follow up I wanted to cover another "Adaptive" feature of your ADF applications, the ability to make multiple different connections from an Application Module, at runtime.
Now, I'm sure you'll be aware that if you define your application to use a data-source rather than a hard-coded JDBC connection string, then you have the ability to change the target of that data-source after deployment to point to a different database. So that's great, but the reality of that is that this single connection is effectively fixed within the application right?  Well no, this it turns out is a common misconception.

To be clear, yes a single instance of an ADF Application Module is associated with a single connection but there is nothing to stop you from creating multiple instances of the same Application Module within the application, all pointing at different connections.  If fact this has been possible for a long time using a custom extension point with code that which extends oracle.jbo.http.HttpSessionCookieFactory. This approach, however, involves writing code and no-one likes to write any more code than they need to, so, is there an easier way? Yes indeed.  It is in fact  a little publicized feature that's available in all versions of 11g, the ELEnvInfoProvider.

What Does it Do? 

The ELEnvInfoProvider  is  a pre-existing class (the full path is  oracle.jbo.client.ELEnvInfoProvider) which you can plug into your ApplicationModule configuration using the jbo.envinfoprovider property. Visuallty you can set this in the editor, or you can also set it directly in the bc4j.xcfg (see below for an example) .
Configuration Editor
Once you have plugged in this envinfoprovider, here's the fun bit, rather than defining the hard-coded name of a datasource instead you can plug in a EL expression for the connection to use.  So what's the benefit of that? Well it allows you to defer the selection of a connection until the point in time that you instantiate the AM.
To define the expression itself you'll need to do a couple of things:
  1. First of all you'll need a managed bean of some sort – e.g. a sessionScoped bean defined in your ViewController project. This will need a getter method that returns the name of the connection. Now this connection itself needs to be defined in your Application Server, and can be managed through Enterprise Manager, WLST or through MBeans. (You may need to read the documentation [http://docs.oracle.com/cd/E28280_01/web.1111/b31974/deployment_topics.htm#CHDJGBDD] here on how to configure connections at runtime if you're not familiar with this) 
  2.  The EL expression (e.g. ${connectionManager.connection} is then defined in the configuration by editing the bc4j.xcfg file (there is a hyperlink directly to this file on the configuration editing screen in the Application Module editor). You simply replace the hardcoded JDBCName value with the expression.  So your cfg file would end up looking something like this (notice the reference to the ELEnvInfoProvider that I talked about earlier)
<BC4JConfig version="11.1" xmlns="http://xmlns.oracle.com/bc4j/configuration">
  <AppModuleConfigBag ApplicationName="oracle.demo.model.TargetAppModule">
    <AppModuleConfig DeployPlatform="LOCAL" 
                     JDBCName="${connectionManager.connection}" 
                     jbo.project="oracle.demo.model.Model" 
                     name="TargetAppModuleLocal"
                     ApplicationName="oracle.demo.model.TargetAppModule">
      <AM-Pooling jbo.doconnectionpooling="true"/>
      <Database jbo.locking.mode="optimistic">
      <Security AppModuleJndiName="oracle.demo.model.TargetAppModule"/>
      <Custom jbo.envinfoprovider="oracle.jbo.client.ELEnvInfoProvider"/>
    </AppModuleConfig>
  </AppModuleConfigBag>
</BC4JConfig>

Still Don't Quite Get It?

So far you might be thinking, well that's fine but what difference does it make if the connection is resolved "just in time" rather than up front and changed as required through Enterprise Manager?
Well a trivial example would be where you have a single application deployed to your application server, but for different users you want to connect to different databases. Because, the evaluation of the connection is deferred until you first reference the AM you have a decision point that can take the user identity into account.

However, think about it for a second.  Under what circumstances does a new AM get instantiated? Well at the first reference of the AM within the application yes, but also whenever a Task Flow is entered -  if the data control scope for the Task Flow is ISOLATED.  So the reality is, that on a single screen you can embed multiple Task Flows, all of which are pointing at different database connections concurrently.

Hopefully you'll find this feature useful, let me know... 


Monday Feb 18, 2013

MySQL & ADF Business Components - Enum types

A quick guide to effectively mapping and representing MySQL enumeration types through ADF Business Components [Read More]

Wednesday Jan 04, 2012

Maintaining Row Currency Separation in Task Flow Instances

So here's a quick thought experiment, how would one create a re-usable task flow which should tick the following boxes:

  1. Multiple copies of the flow are displayed on a single page
  2. Each instance of the taskflow should maintain it's own row currency and not be affected by navigation in any other instance
  3. All instances of the taskflow should be part of the same transactional context

Let's analyse that. Well, Requirement (1) is easy, that's one of the core capabilities of task flows.  Each has it's own pageFlowScope which is kept private so multiple instances on the page should be fine.

Requirement (2) can be achieved simply by setting the data control scope to <isolated/> in the task flow definition. 

Requirement (3) - oh dear.

So there's the problem, it seems that requirements (2) and (3) are mutually exclusive.  If you want everything to use the same transaction you also have to have them sharing the same VO instance and therefore being coordinated from the record currency point of view. 

To solve this let's just think a little outside the box and consider what row currency is and therefore how we could approach the problem.  Each View Object Instance has a primary RowSetIterator  which identifies the "current row" within the VOs collection. As all the instances of the TaskFlow are using the same bindings, they are therefore using the same VO instance and therefore all share the same RowSetIterator (RSI). Now a VO instance can have secondary RSIs but these are really only of use in the programmatic sense, and cannot be wired in through the binding layer.

So logically, one approach to this is to define a second VO instance based on the same VO definition and bind one taskflow to that and the other to the origional. Well that's fine, but we're painted into a corner here, in that we need to decide up-front how many concurrent views we need and then define explicit VO instances in the AM  and separate almost-identical-apart-from-the-bindings taskflows. So this may well work when you just have a couple of views but then it get's unworkable, plus it just feels wrong to duplicate all that taskflow content just to change the VO instance name.

However, in principle, the idea is along the right lines. We just need to simplify it so that we don't have to do any duplication or pre-definition of VO instances. Is it possible? Yes of course, and surprisingly easy.

The Approach 

Getting Started 

The first step here is to create your AM with an initial instance of the VO you need exposed (you can delete it later but having it there will make the design time easier). Once the VO instance is available in the data control palette you can go ahead and create your re-usable taskflows and test them you want to get the basic functionality right after all.  Of course at this stage if you use multiple instances  of the taskflow they will all be coordinated. 

Parameterise the Task Flow  

 At this stage define a task flow parameter which can be passed in to define the unique name for the VO instance that this instance of the taskflow will use. You might write some fancy generator method to create unique names in sequence (DeptView2, DeptView3, DeptView4 and so on) or just hardcode a value when you map the taskflow into a region, it's up to you. Just bear in mind that any taskflow instances that share the same instance name will also share the same RSI and therefore be coordinated - so you can mix and match both coordinated and uncoordinated instances. For the sake of example let's call that parameter pVOInstanceName.

Create a Method to Create  New Instances of the VO

Next we need to define a method that will create a VO instance on demand. This is very simple code, just generate up a Java Impl class for your AM and add a method something like this:

     public void createUniqueVOInstance(String voDefName, String instanceName){
        ViewObject existingVO = findViewObject(instanceName);
        if (existingVO == null) {
            _logger.info("Creating VO instance for " + instanceName);
            ViewObject newVO = this.createViewObject(instanceName, voDefName);
        }
        else {
            _logger.info("Reusing VO instance for " + instanceName);
        }
      }

Note how we check to see if that name is already in use first. 

Expose this method as part of the client interface so we can bind that into the taskflow.

Invoke the VO Instance Creation Method from the TaskFlow

Once you've refreshed the data control pallet you should then be able to drag this new AM method (createUniqueVOInstance in my case) into the task flow.  Set that as the default activity on on the flow so that it executes first. You'll pass the full name of the ViewObject definition (e.g. oracle.demo.model.DepartmentsView) into the voName parameter and the instance name you want will come from the parameter you defined for the taskflow (e.g. #{pageFlowScope.pVOInstanceName}). So now when you enter the flow you'll create a new VO instance based on the supplied definition with this name. 

Naturally you then wire this method activity to the rest of the taskflow as normal so that once the instance has been created you can continue to display the page fragments etc.

Rewire the Iterators. 

The final piece of the puzzle is how to tell all of the bindings that you've created within the taskflow to use this newly created VO instance rather than the one that they where created with. This final step is very simple and elegant. for the views and other activities in the taskflow just edit the pageDef XML files and locate all of the iterator bindings that relate to your template VO instance that you used when creating the UI through drag and drop. Now simply change the hard-coded reference to the VO Instance name in the Iterator binding Binds attribute to the expression pointing to your new instance name. e.g. 

  <executables>
    <iterator Binds="#{pageFlowScope.pVOInstanceName}" RangeSize="25"
              DataControl="AppModuleDataControl" id="DepartmentsView1Iterator"
              ChangeEventPolicy="ppr"/>
 </executables> 

And there you go, nothing else has to change.  Just be sure to make this change throughout all of the pageDefs used by the taskflow otherwise you're in for some funny results.

So there you have it, transactionally linked but independently scrolling instances of the same taskflow. 

And there's more

Once you start to think about it, things get even more interesting here. Because each instance of the taskflow has it's own VO  instance they can also have their own set of view criteria applied and yet all still be visible on the screen at once. 

Tuesday Jun 08, 2010

Browsing Your ADF Application Module Pooling Params with WLST

In ADF 11g you can of course use Enterprise Manager (EM) to browse and configure the settings used by ADF Business Components  Application Modules, as shown here for one of my sample deployed applications.
em_adfpooling.png

This screen you can access from the EM homepage by pulling down the Application Deployment menu, and then ADF > Configure ADF Business Components. Then select the profile that you are actually using (Hint: look in the DataBindings.cpx file to work this out - probably the "Local" version unless you've explicitly changed it. )
So, from this screen you can change the pooling parameters and the world is good. But what if you don't have EM installed? In that case you can use the WebLogic scripting capabilities to view (and Update) the MBean Properties.
Explanation
The pooling parameters and many others are handled through Message Driven Beans that are created for the deployed application in the server. In the case of the ADF BC pooling parameters, this MBean will combine the configuration deployed as part of the application, along with any overrides defined as -D environment commands on the JVM startup for the application server instance.

Using WLST to Browse the Bean Values
For our purposes here I'm doing this interactively, although you can also write a script or write Java to achieve the same thing.

Step 0: Before You Start
You will need the following
  1. Access to the console on the machine that is running the server or another machine on the same network that has the same version of WebLogic
  2. The WebLogic Admin username and password (I'll use weblogic/password as my example here - yours will be different)
  3. If you are connecting to a remote server the network path to the listen address and port of the running server (for example t3://adf.example.com:7001)
  4. The name of the deployed application (in this example FMWdh_application1)
  5. The package path to the bc4j.xcfg file (in this example oracle.demo.fmwdh.model.service.common.bc4j.xcfg) This is based on the default path for your model project so it shoudl be fairly easy to work out.
  6. The BC configuration your AM is actually running with (look in the DataBindings.cpx for that. In this example DealHelpServiceDeployed is the profile being used..)
Step 1: Start the WLST console
To start at the beginning, you need to run the WLST command but that needs a little setup:
  1. Change to the wlserver_10.3/server/bin directory e.g. under your Fusion Middleware Home
    [oracle@mymachine] cd /home/oracle/FMW_R1/wlserver_10.3/server/bin
  2. Set your environment using the setWLSEnv script. e.g. on Oracle Enterprise Linux:
    [oracle@mymachine bin] source setWLSEnv.sh
  3. Start the WLST interactive console
    [oracle@mymachine bin] java weblogic.WLST
Initializing WebLogic Scripting Tool (WLST) ...
Welcome to WebLogic Server Administration Scripting Shell
Type help() for help on available commands

wls:/offline>


Step 2:Enter the WLST commands
  1. Connect to the server
    wls:> connect('weblogic','password')
    Or if it is a remote server
    wls:> connect('weblogic','password','t3://adf.example.com:7001')
  2. Change to the Custom root, this is where the AMPooling MBeans are registered
    wls:> custom()
  3. Change to the b4j MBean directory
    wls:> cd ('oracle.bc4j.mbean.config')
  4. Work out the correct directory for the AM configuration you need. This is the difficult bit, not because it's hard to do, but because the names are long. The structure here is such that every child MBean is displayed at the same level as the parent, so for each deployed application there will be many directories shown. In fact, do an ls() command here and you'll see what I mean. Each application will have one MBean for the app as a whole, and then for each deployed configuration in the .xcfg file you'll see: One for the config entry itself, and then one each for Security, DB Connection and AM Pooling. So if you deploy an app with just one configuration you'll see 5 directories, if it has two configurations in the .xcfg you'll see 9 and so on.
    The directory you are looking for will contain those bits of information you gathered in Step 0, specifically the Application Name, the configuration you are using and the xcfg name:
    First of all narrow your list to just those directories returned from the ls() command that begin oracle.bc4j.mbean.config:name=AMPool. These identify the AM pooling MBeans for all the deployed applications.
    Now look for the correct application name e.g. Application=FMWdh_application1
    The config setting in that sub-list should already be correct and match what you expect e.g. oracle.bc4j.mbean.config=oracle.demo.fmwdh.model.service.common.bc4j.xcfg
    Finally look for the correct value for the AppModuleConfigType e.g. oracle.bc4j.mbean.config.AppModuleConfigType=DealHelpServiceDeployed
  5. Now you have identified the correct directory name, change to that (keep the name on one line of course - I've had to split it across lines here for clarity:
    wls:> cd ('oracle.bc4j.mbean.config:name=AMPool,
        type=oracle.bc4j.mbean.config.AppModuleConfigType.AMPoolType,
        oracle.bc4j.mbean.config=oracle.demo.fmwdh.model.service.common.bc4j.xcfg,
        Application=FMWdh_application1,
        oracle.bc4j.mbean.config.AppModuleConfigType=DealHelpServiceDeployed')
  6. Now you can actually view the parameter values with a simple ls() command
    wls:> ls()
    And here's the output in which you can view the realtime values of the various pool settings:

-rw- AmpoolConnectionstrategyclass oracle.jbo.common.ampool.DefaultConnectionStrategy
-rw- AmpoolDoampooling true
-rw- AmpoolDynamicjdbccredentials false
-rw- AmpoolInitpoolsize 2
-rw- AmpoolIsuseexclusive true
-rw- AmpoolMaxavailablesize 40
-rw- AmpoolMaxinactiveage 600000
-rw- AmpoolMaxpoolsize 4096
-rw- AmpoolMinavailablesize 2
-rw- AmpoolMonitorsleepinterval 600000
-rw- AmpoolResetnontransactionalstate true
-rw- AmpoolSessioncookiefactoryclass oracle.jbo.common.ampool.DefaultSessionCookieFactory
-rw- AmpoolTimetolive 3600000
-rw- AmpoolWritecookietoclient false
-r-- ConfigMBean true
-rw- ConnectionPoolManager oracle.jbo.server.ConnectionPoolManagerImpl
-rw- Doconnectionpooling false
-rw- Dofailover false
-rw- Initpoolsize 0
-rw- Maxpoolcookieage -1
-rw- Maxpoolsize 4096
-rw- Poolmaxavailablesize 25
-rw- Poolmaxinactiveage 600000
-rw- Poolminavailablesize 5
-rw- Poolmonitorsleepinterval 600000
-rw- Poolrequesttimeout 30000
-rw- Pooltimetolive -1
-r-- ReadOnly false
-rw- Recyclethreshold 10
-r-- RestartNeeded false
-r-- SystemMBean false
-r-- eventProvider true
-r-- eventTypes java.lang.String[jmx.attribute.change]
-r-- objectName oracle.bc4j.mbean.config:name=AMPool,type=oracle.bc4j.mbean.config.AppModuleConfigType.AMPoolType,oracle.bc4j.mbean.config=oracle.demo.fmwdh.model.service.common.bc4j.xcfg,Application=FMWdh_application1,oracle.bc4j.mbean.config.AppModuleConfigType=DealHelpServiceDeployed
-rw- poolClassName oracle.jbo.common.ampool.ApplicationPoolImpl
Thanks to Brian Fry on the JDeveloper PM Team who did most of the work to put this sequence of steps together with me badgering him over his shoulder.
Additional thanks to James Bayer who pointed out that we can connect remotely to the server, allowing the same script to be run from a single point against many servers.
About

Hawaii, Yes! Duncan has been around Oracle technology way too long but occasionally has interesting things to say. He works in the Development Tools Division at Oracle, but you guessed that right? In his spare time he contributes to the Hudson CI Server Project at Eclipse
Follow DuncanMills on Twitter

Note that comments on this blog are moderated so (1) There may be a delay before it gets published (2) I reserve the right to ignore silly questions and comment spam is not tolerated - it gets deleted so don't even bother, we all have better things to do with our lives.
However, don't be put off, I want to hear what you have to say!

Search

Archives
« April 2014
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today