|
Friday, August 01, 2003 |
"How could I program my View Object to provide default values for its bind variables if none has been already supplied programmatically?"
Suppose you have a DeptView with a query like this:
SELECT Dept.DEPTNO, Dept.DNAME, Dept.LOC FROM DEPT Dept WHERE deptno = :0 and loc = :1
Then, you could override the executeQueryForCollection() method in your view object implementation class to conditionally default values for these two bind variables using code like this:
/** * Overridden framework method to optionally setup default values for * all user-supplied bind variables if no user-supplied bind variables * have already been set programmatically. If any framework-supplied bind * values are already supplied, they will come *AFTER* the "numUserParams" * number of user-supplied bind values. Framework-supplied bind variables * could be a side-effect of automatic master/detail coordination, for example. */ protected void executeQueryForCollection(Object qc, Object[] params, int numUserParams) { /* * Assume that we only will default bind variables if NONE has already * been specified by the user... */ if (numUserParams == 0) { /* * You could include more elements in this array to default the values * of multiple bind variables. In this example, we're supplying default * values for the two bind variables in the query. */ Object[] defaultValuesForBindVars = new Object[]{ new Number(10), "NEW YORK"}; numUserParams = defaultValuesForBindVars.length; int numFwkSuppliedBindVals = (params != null) ? params.length : 0; if (numFwkSuppliedBindVals > 0) { /* * Allocate a new Object[] with enough room for both the * user bind values and the framework supplied bind values */ Object[] newBinds = new Object[numFwkSuppliedBindVals + numUserParams]; /* * Copy the framework-supplied bind variables into a new Object[] * leaving 'numUserParams' slots at the beginning of the array to * add in the user supplied bind values *before* the framework * supplied bind values. */ System.arraycopy(params,0,newBinds,numUserParams,numFwkSuppliedBindVals); /* * Copy the user-supplied bind variables to the front of the newBinds * Object array. */ System.arraycopy(defaultValuesForBindVars,0,newBinds,0,numUserParams); params = newBinds; } else { params = defaultValuesForBindVars; } } super.executeQueryForCollection(qc, params, numUserParams); }
I've written the code to work for defaulting any number of initial bind variables, but it has a simplifying assumption that we only default all of the user bind variables, or none (in the case that some were already set programmatically).
If the VO's are involved in View Links, then you need to pay attention to the framework-supplied bind variables that BC4J will tack on to support the extra view link where clause that it will glue on.
2:50:24 PM
|
|
"When should I use Configuration.createRootApplicationModule() , and when not?"
There are various ways to lookup and create an application module. The most low-level way that gives you the most control is to lookup the home interface for the application module using JNDI, and using that home interface as a factory to create yourself an instance of the application module. This approach follows the same pattern to how you use JNDI to lookup and create EJB Session Bean components. The code looks like this:
import java.util.Hashtable; import javax.naming.InitialContext; import javax.naming.NamingException; import oracle.jbo.ApplicationModule; import oracle.jbo.ApplicationModuleHome; import oracle.jbo.JboContext; : etc : String AMDefName = "com.oracle.apps.hr.personnel.HiringModule"; Hashtable env = new Hashtable(2); env.put(JboContext.INITIAL_CONTEXT_FACTORY,JboContext.JBO_CONTEXT_FACTORY); env.put(JboContext.DEPLOY_PLATFORM, JboContext.PLATFORM_LOCAL); ApplicationModule am = null; try { InitialContext ic = new InitialContext(env); ApplicationModuleHome home = (ApplicationModuleHome)ic.lookup(AMDefName); am = home.create(); } catch (NamingException nex) { /* Handle the error here if you don't find it */ } /* * Connect the AM to a database connection */ am.getTransaction().connect("...JDBC Connection String...");
You can manually populate all of the configuration parameters into the Hashtable that you pass to the JNDI InitialContext constructor, and there are various different platform "modes" that you can use depending on whether you're client code that wants to use the AM instance is using it in local mode, over an EJB RMI connection. etc. Creating an AM in this way is not using any kind of AM pool. It's doing things the hard way, but it can be useful to understand that this approach exists to appreciate the other layers we offer above it to simplify the most common use cases.
Rather than manually setting up all of those configuration parameters manually, BC4J offers the notion of a Configuration for your application module. A configuration is basically a design-time-defined set of name/value pairs that you can configure using the right-mouse "Configurations..." menu option on your application module component. Some helpful UI assists you in setting what configuration parameters you might want to change from their defaults, and then your settings are saved in the bc4j.xcfg file in the ".common" subpackage directory of the package directory where your application module lives. At runtime, we can populate that Hashtable of parameters for you based on the name/value pairs that you've configured in your configuration.
For simple console/batch programs, you can avoid writing all of that code above by simply configuring your desired parameters at design and then using the createRootApplicationModule() method on the Configuration object. Under the covers, we've written the more compliated code above, so you can write one line of code to replace all of the above:
ApplicationModule am = Configuration.createRootApplicationModule( "com.oracle.apps.hr.personnel.HiringModule", "HiringModuleLocal");
Much simpler! However, it's good to understand when is the right time to use this method. In a nutshell, the createRootApplicationModule() method is right for two situations:
- Batch/console style programs that need to work with an application module
- Completely stateless web applications which are not already using the BC4J/Struts integration or <jbo:ApplicationModule> tag
In both of the above two cases, if you use createRootApplicationModule() to acquire the application module, make sure to also use Configuration.releaseRootApplicationModule(ApplicationModule, boolean) to release the application module. If you pass true for the boolean parameter of this method, then the AM instance is immediately cleaned up. If you pass false, then it will be kept around in the application module pool for subsequent reuse.
If you are using the BC4J/Struts integration, then your application module acquisition and release is performed automatically on your behalf by the BC4JRequestProcessor as described in this section of the BC4J Toy Store whitepaper. If you are not using a controller, and instead are using the <jbo:ApplicationModule> tag to acquire an application module from the pool, make sure to always use a corresponding <jbo:ReleasePageResources/> tag to free it.
Never combine the two mechanisms. That is, if you are working with an AM that has been acquired by the BC4J/Struts or the <jbo:ApplicationModule> tag, never try to pass that AM instance to the Configuration.releaseRootApplicationModule() method. These two approaches use different underlying implementations for keeping track of user sessions and are not intended to be intermixed.
1:25:43 PM
|
|
"What really happens when you release an application module in Stateful mode to the application module pool?"
When you release an application module in stateful mode, you are asking BC4J to manage its pending transaction state for you. If you are running with the "jbo.dofailover=true" (in "Failover mode"), then immediately upon stateful release of your AM instance, we take an XML-based snapshot of the pending information in your transaction, along with some information about the active iterators in the application module and their current row number. By "pending changes", I mean any unposted entity instance changes in the cache. Those could of course be new entities, changed entities, or deleted entities which have not yet been posted or committed to the database. It a sort of application-level (AM-scoped) redo log. If you are not running in "Failover Mode", then this snapshot is not taken as aggressively, and it will only be taken as needed. More on the "as needed" part in a second.
By design, the AM pool use an algorithm called "Stateless with (user session) Affinity". This means that it goes out of its way to attempt to give you back the same AM instance from the pool that your user session was using on the last request, when you come in for the next request. If your pool is sized correctly for your typical application load, then most of the time your user session should be reunited with the same AM instance it was using. If the load on the system (given the configuration parameters of your AM pool) is such that another user session needed an AM instance from the pool since your last stateful AM release, and there were no other AM instances available to give the other session, your "preferred" AM instance might have been handed out for use by another session. Before doing this, the AM pool snapshots your AM's current pending state, rollbacks the AM, and then possibly reactivates the pending state information for the user session that it's handing that AM instance to. The same thing will happen to your user session the next time it comes in and discovers that its "preferred" AM instance is no longer available. Any available AM will be selected, and the snapshot of your AM's previous pending state will be used to reinstate the pending state you had in the AM when you last did your stateful release at the end of your previous request.
The net net, is that you will always get back an AM in the same pending state that you left it in on the previous stateful AM release to the pool. In the optimum case, you will have exactly the same AM instance and no reactivation-from-snapshot is necessary. In the worst case (but without your needing to care about it) we might need to use the state snapshot to put some other AM instance back into the same state you were expecting it to be in.
This gives a resource utilization that is very close to that of a completely stateless application, with the programming simplicity of being able to assume the application's pending data is stateful.
12:16:14 PM
|
|
© Copyright 2008 Steve Muench.
|
|