Thursday Mar 01, 2012

uncommittedDataWarning - It's a Matter of Timing

An interesting nugget came across my desk yesterday that I though was worth sharing. What's wrong with this picture:

Task flow Diagram

Absolutely nothing right?  That's exactly the kind of taskflow we would expect to see where the user can, from the listEmployee screen, either create a new empty record by following the new navigation rule or edit the current row using the edit rule. The problem in this particular case was, however, that the employeeList screen has uncommittedDataWarning="on" set as an attribute on the document component:

<af:document title="Employee List" 
             uncommittedDataWarning="on"> 

So what happens? Every time the new navigation rule is followed to navigate to the editEmployee screen the uncommitted data warning pops up.  So what's going on here? The listEmployee screen is read only in this case how could it have marked the data control as dirty? 

Well it all comes down to the timing of when the uncommittedDataWarning takes place. The check will be made when the JSF View ID is being changed.  If you look at the above taskflow you'll see that the createInsert method binding will take place before the navigation away from the page, so at the point in time that the continue navigation is invoked the DataControl will be dirty after all and the uncommittedDataWarning check will fail, popping up the dialog. 

The solution in this case is simple (assuming that you have to keep the uncommittedDataWarning="on" on the source page). You can revert to the technique that we used to use in ADF before TaskFlows came along.  You create the method binding for the createInsert operation in the employeeEdit Screen and add an invokeAction in the executables section of the pageDef which is conditional on a flag set on the request. Something like this:

<invokeAction id="InvokeCreateIfRequired" 
              Binds="CreateInsert" 
              RefreshCondition="#{requestScope.editAction eq 'INSERT'}"
              Refresh="ifNeeded"/> 

Then the command item that triggers the navigation to the edit page sets that flag (or does not set it if you want to do an edit rather than an insert) :

<af:commandButton text="New" id="cb1" action="new"> 
   <af:setPropertyListener from="#{'INSERT'}" 
                           to="#{requestScope.editAction}" 
                           type="action"/>
</af:commandButton>

So there you go - there is life in the old techniques yet! 

Saturday Jan 21, 2012

Break Group Formatting in a Table - Part 2

In part 1 of this series I discussed the use of the EL map mechanism as a way that we could fake a function call to manage the logic of the Break Group value display.  In this article I wanted to discuss an alternative which is to actually extend Expression Language and add a custom function to do the job in a "proper" way. 

In doing this I've taken the opportunity to make the code more flexible and generic than the Map example by allowing breaking at multiple levels and breaking in multiple tables in the same view. All in all this is a much cleaner and really simpler solution than that covered in Part 1.

Defining the Function

The EL function that I'm using here is defined in a static method within a class in the project.  It is of course something that could be easlily bundled up into a re-usable JAR but in this case I've done everthing in the ViewController project. Note that I'm using 11.1.2 here and a facelets based page, but this process is essentially the same with 11.1.1.n and JSP(X) based pages.

Here's the function class. It just defines the single static method to do the check.  Notice that this function returns a Boolean to indicate a match with the previous value, but as arguments it takes the value to compare and a key.  This key is an arbitrary String which will allow us to manage several parallel compares. For example if we wanted two break group tables on the same page we would use a unique key value for each. Likewise if you wanted to have more than one break column in a single table you just need to give each a unique identified for this value.  Secondly I've genericized the code to use Object as the type of the compare value so you should be able to break on any attribute type. 

package oracle.demo.breakgroup.el;

import java.util.HashMap;
import java.util.Map;
import oracle.adf.view.rich.context.AdfFacesContext;

public final class BreakGroupFunctions {

  //Key used to store the map we use in ViewScope
  private final static String BREAK_GROUP_STORE = "BREAK GROUP STORE";
  /**
   * EL function to compare a suuplied value with the previous value checked for the same key
   * @param compareKey the key of the value to be checked - usually unique identifies a tabel in the UI
   * @param compareValue the value to compare with the previous value supplied for this key
   * @return True if matches the previously checked value for this key
   */ 
  public static Boolean compareWithLastValue(String compareKey, Object compareValue ){
    Boolean repeatedValue = false;
    AdfFacesContext actx = AdfFacesContext.getCurrentInstance();
    Map viewScopeMap = actx.getViewScope();
    Map bgs;
    if (viewScopeMap.containsKey(BREAK_GROUP_STORE)){
      bgs = (Map)viewScopeMap.get(BREAK_GROUP_STORE);
    }
    else{
      // First access so create and populate the store map
      bgs = new HashMap(1);
      viewScopeMap.put(BREAK_GROUP_STORE, bgs);
    }
    if (bgs.containsKey(compareKey)){
      Object compareLast = bgs.get(compareKey);
      if (compareLast != null && compareLast.equals(compareValue) ){
        repeatedValue = true;
      }
      else{
        // new value, so reset what we'e got stored
        bgs.put(compareKey,compareValue);
      }
    }
    else {
      // This must be a new key so store it away
      bgs.put(compareKey, compareValue);
    }
    return repeatedValue;
  }
}

Define the TagLib

JDeveloper has a handy editor to create the Tag library (New -> Web Tier -> JSF / Facelets -> Facelets Tag Library). This will allow you to define either custom components or functions, as we are doing here.  You basically need just 3 bits of information to define your function in the taglib, the class you created, the function signature in the class and an arbitary namespace string to uniquely identify the taglib.

The resulting XML is pretty simple. Notice the <namespace> attribute which will be referenced again in the <f:view> tag of the page.

<?xml version = '1.0' encoding = 'windows-1252'?>
<facelet-taglib xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
                version="2.0" xmlns="http://java.sun.com/xml/ns/javaee">
  <namespace>oracle.com/adf/demo/breakgroup.taglib</namespace>
  <function>
    <description>Compares the passed-in value with the last one that was passed in for this key and returns true to indicate a match or false in any other case.</description>
    <function-name>compareWithLast</function-name>
    <function-class>oracle.demo.breakgroup.el.BreakGroupFunctions</function-class>
    <function-signature>java.lang.Boolean compareWithLastValue(
                        java.lang.String, java.lang.Object)</function-signature>
  </function>
</facelet-taglib>

Wire this into the Page

So finally we need to be able to consume this new function in the page.  The first step is to create the namespace definition in the <f:view ...> tag, in this case I've used "bgf", thus:

<f:view xmlns:f="http://java.sun.com/jsf/core" 
        xmlns:af="http://xmlns.oracle.com/adf/faces/rich" 
        xmlns:bgf="oracle.com/adf/demo/breakgroup.taglib">

Then we can use that function in much the same way as the map reference we used before, but now we can pass two arguments - the value to compare and the compare key:

<af:column headerText="Department" id="c1">
  <af:outputText 
           value="#{row.DepartmentName}" 
           visible="#{!bgf:compareWithLast('departmentsTable_bg1', row.DepartmentId)}" 
           id="ot1"/>                            
</af:column>   

So there you have it. As you can see it's really simple to extend Expression Language for yourself and in doing so opens up a large set of possibilities. Before I go though I'll point you off to a couple of other resources in this subject area which will provide a little more. 

Wednesday Jan 11, 2012

Axis Formatting in DVT Gauge

Further to my last article on gauge style UIs, I though that I'd write up a little more on the core gauge control itself, focusing this time on formatting the axis of the gauge. 

In our original example we just used the defaults for the axis which produced a gauge like this (I've removed the reference marker line that I was focusing on last time).

Defaul Gauge Axis

As you can see the default is to draw tick labels and values at the min/max and at the threshold boundaries.  This is a pretty good set of defaults, but Hugh on the development team pointed out that there is more flexibility than that, so let's explore the options.

The good stuff here is provided by a couple of child tags to the gauge, <dvt:tickLabel> and <dvt:tickMark> these tags allow you to control the vertical tick marks and the text separately and we'll see an example of that a little later. The key attribute here for both of these tags is the content attribute, this takes one or more string constants as a space or comma separated list so you can combine several options together (see example below). Here are the constants:

TC_NONE No label or tick-mark is displayed
TC_MIN_MAX Display label or tick-mark at either end of the gauge
TC_METRIC Display label or tick-mark at the data value
TC_THRESHOLD  Display label or tick-mark at each threshold boundary
TC_INCREMENTS Displays labels or tick-marks at regular intervals as defined by the <dvt:tickMark> majorIncrement property. Note that for the label you cannot mix this attribute with the TC_THRESHOLD or TC_METRIC 
TC_MAJOR_TICK

Displays labels or tick-marks at min/max and the majorIncrement value - effectively the same as "TC_INCREMENTS TC_MIN_MAX". Note that for the label you cannot mix this attribute with the TC_THRESHOLD or TC_METRIC 

We can combine those in different ways, for example to produce this example where we mark the min, max and current values in terms of text but only show a tick-mark at the value (TC_METRIC):

gauge with just the metric tick

The code for this one is:

<dvt:gauge ....>
     <dvt:tickLabel content="TC_MIN_MAX TC_METRIC"/>
     <dvt:tickMark content="TC_METRIC"/> 
...

Or this where we show a more regular axis, similar to the one that we produced when emulating the gauge with the horizontalBar chart:

Gauge with regular increments

And the code:

<dvt:gauge ....>
  <dvt:tickLabel content="TC_MAJOR_TICK"/>
  <dvt:tickMark content="TC_MAJOR_TICK" majorIncrement="30"/>
...

 Finally I wanted to explore one more setting within gauge that allows you to combine the thermometer style bar gauge with the LED style gauge. If you're not familiar with the LED style gauge, it's simply a coloured shape in various styles (circular, arrow, triangle or your own custom image) that changes colour to reflect the relationship between the control value and the threshold ranges. These are usually used for traffic light style indications on dashboards.

One of the things that we can do with the STATUSMETER style of gauge that we've been using here is to colour the whole gauge based on where the value is in respect to the threshold values. So a value of 170 on my gauge example will render like this:

usethresholdColor green gauge

And a value of 15 which is down in the red zone will render like this:

useThresholdFillColor red gauge

The code is extremely simple for this one, just set the useThresholdFillColor attribute to true on the <dvt:gaugePlotArea> tag, another child of <dvt:gauge> and then the correct fill colour will be picked up from your threshold definitions.

Happy gauge-ing! 

Tuesday Jan 10, 2012

Ever Wondered how uncommittedDataWarning Works?

You may have come across the uncommittedDataWarning attribute on the <af:document> tag.  With this attribute switched to "on" the framework will pop up a dialog like this when you try and navigate away from the page with the possibility of loosing the change: 

Browser dialog for unsaved changes

 What if you wanted to check yourself, in a programmable way or from an EL expression, against the same data so that you could, for example, popup your own dialog or mark a "save" menu item as enabled. Is it possible? Well yes of course and really very neat. Here's the code snippet (thanks to Dave S. who gave me this hint ages ago ) 

 ViewPortContext rootViewPort = ControllerContext.getInstance().getCurrentRootViewPort();
 boolean uncommittedChanges = rootViewPort.isDataDirty();

This simple snippet will query all the transactional data controls on the page and in all regions in the page for their dirty status and deiver a simple boolean result to you.

Sunday Jan 08, 2012

An Enhanced Gauge control using HorizontalBar

I thought this one was worth writing up because it highlights a couple of really nice features within the ADF DVT charts -  Reference Objects and Alerts. The scenario was based on a problem where there was a requirement for a conventional horizontal gauge control, showing the normal thresholds for the control - shown in this image here as the red, yellow and green zones:

Basic Gauge

The twist in this case was that we required an extra reference marker on the data bar that indicated the "optimal" value within a particular threshold. So in the image above imagine that within the green zone, 150 was the optimal value and we need to somehow indicate that.

Simply using the gauge it is possible to do this using an extra threshold value at the required value, or rather two extra threshold definitions, we have to terminate the threshold that currently starts at 90 at 150 (the reference value we want to mark), then a short threshold from 150-151 just to provide the reference marker we need, then an extra green threshold from 152 to 200 to complete the green bar.  You end up with this:

Gauge with reference line

So that works pretty well - but it's a limitation of the use of thresholds that all you can achieve is a block of colour or line behind the indicator bar, plus the fact that I've used an extra level of the thresholdset here means it's not very dynamic (although of course it could be if you want to start writing code). Think about if we wanted to overlay several different dynamic markers such as min/max levels reached in the last 24 hours. That would be do-able but it is getting messy. 

Another approach to take is to use the bar chart (horizontal in this case) to simulate the gauge. We don't have the dvt:threshold /dvt:thresholdSet  in the case of charts but we do have the equivalent with dvt:referenceObject. Reference objects allow you to place a filled area or line in front or behind the data series so we can emulate the same look that we had with the gauge:

Gauge using horizontal bar chart

As you can see it looks pretty similar, although there are some slight differences:

  1. Unlike the gauge, which displays value labels at the threshold boundaries, the axis on the chart has a regular labelling at fixed intervals based on the y1Axis setting.
  2. We're missing the tick marks between the Y axis and the labels - well in fact that's a slight bug, they are there, but the size in proportional to the height of the chart (46px in this case). In 11.1.2 and above you should see them correctly.
  3. The proportions of the series bar / chart area are  slightly different to the gauge. But that's only noticeable if you are mixing and matching.

 Let's break down how to create some of the features here:

Overall Size

The height / width of the cart had to be controlled somewhat to bring it down to gauge dimensions.  This is acheived using inlineStyle on the horizontalBarGraph tag:

<dvt:horizontalBarGraph id="gaugeClone"
                        value="#{bindings.GaugeChartData.graphModel}"
                        subType="BAR_HORIZ_CLUST"
                        inlineStyle="width:400px;height:46px;"> 

We also need to ensure that the y axis is fixed. By default it will be scaled based on the max value of the data which we don't want. To do this we define the min/max values on the nested y1Axis tag and set the axisMaxAutoScaled attribute to false. We also define the tickmark label interval to 30 here.

<dvt:y1Axis axisMinValue="0"
            axisMaxValue="280"
            majorIncrement="30"
            axisMaxAutoScaled="false"/> 

Bar Styling

By default the gauge has that grey-ish colour  whereas the deafult colour for the first series in a bar chart will be a blue (which is nice, but for the sake of consistency I wanted to change). The colours / shapes used for bars, lines and markers in charts are all controlled by dvt:series tags. So here I've set up the values for series 0 which defines the bar data and set both the fill and the border to emulate the look of the gauge. Note that the series tag needs to be wrapped in a seriesSet.

<dvt:seriesSet>
  <dvt:series index="0" 
              color="#99ccff" 
              borderColor="#848484"
              borderTransparent="false"/>
</dvt:seriesSet>

Threshold banding

Next we want to add the banding to emulate the gauge thresholds. To do this we use the referenceObject tag with the RO_AREA type set to make it fill the defined area rather than draw a line. Again the referenceObject tags need to be enclosed in a parent, referenceObjectSet:

<dvt:referenceObjectSet>
   <dvt:referenceObject index="1" type="RO_AREA"
                        association="SERIES" location="RO_BACK"
                        color="#ff0000"
                        lowValue="0" highValue="30"/>
   <dvt:referenceObject index="2" type="RO_AREA"
                        association="SERIES" location="RO_BACK"
                        color="#f7ffd6" 
                        lowValue="30" highValue="90"/>
   <dvt:referenceObject index="3" type="RO_AREA"
                        association="SERIES" location="RO_BACK"
                        color="#00ff00" 
                        lowValue="90" highValue="200"/>
   <dvt:referenceObject index="4" type="RO_AREA"
                        association="SERIES" location="RO_BACK"
                        color="#f7ffd6" 
                        lowValue="200" highValue="230"/>
   <dvt:referenceObject index="5" type="RO_AREA"
                        association="SERIES" location="RO_BACK"
                        color="#ff0000" 
                        lowValue="230" highValue="280"/>
</dvt:referenceObjectSet> 

The Reference Line 

Just like with the gauge we manipulate the reference object set to add the reference line at 150. However, reference object actually has a line subtype so we can simply use that rather than having to use an area with width of 1. We add the following into the referenceObjectSet:

<dvt:referenceObject index="6" type="RO_LINE" lineValue="150.0"
                     association="SERIES" location="RO_BACK"
                     color="#000000" /> 

The nice thing here is that we can flip this line so it overlays the series. This is not something we can do with Gauge:

<dvt:referenceObject index="6" type="RO_LINE" lineValue="150.0"
                     association="SERIES" location="RO_FRONT"
                     color="#000000" /> 

Which gives us this:

Bar based gauge with overlay reference

The lineValue attribute can, of course, be an EL expression rather than a hard-coded value so you can make the reference point dynamic.

Using Alerts to Add Markers

The final twist is to move away from these plain lines that we've been using as marker values so far and just retain the referenceObjectSet for the threshold banding.  Graph supplies a second "set" of things - the alertSet, which will allow us to overlay gif and png images over the series. Using that we can overlay multiple markers that look a little more attractive - like this:

Bar based gauge with overlay marker icons

You could imagine this being used to reflect min / max values or that kind of thing, it's a really neat capability. To do this all we need to do is add a simple alertSet (note that all of the hardcoded values here could be replaced by Expression Language for a dynamic gauge).

<dvt:alertSet>
   <dvt:alert xValue="Power Consumption"
              yValue="150" yValueAssignment="Y1AXIS"
              imageSource="/images/slider.png"/>
   <dvt:alert xValue="Power Consumption"
              yValue="220" yValueAssignment="Y1AXIS"
              imageSource="/images/slider.png"/>
</dvt:alertSet>

The xValue attribute maps the alert marker onto the required series bar.

Finally, just for fun, see if you can work out how to do this one:

The puzzle

Answers in a comment please...

Final Thoughts

So should you use this technique rather than the out of the box gauge control? Well only of you really need to.  Gauge is simple and lightweight and if all you need is a simple reference line, thresholds do the trick quite well. However, this technique does go to show that if  you think out of the box a little you can do a lot with the DVT tools at your disposal. If nothing else you now know about referenceObjects and alerts!

Thursday Dec 22, 2011

Broken Link Fixed - ADF and Servlets

I've just noticed that one of the articles that people actually refer to (Using ADF and Servlets together) still had a link back to the old Samplecode site and so the example was no longer available. I've now corrected that for you. Enjoy. 

Wednesday Dec 21, 2011

ADF Performance Presentation from UKOUG

I've given this presentation a few times now at various events around the world, most recently at the UK Oracle Users Group. It's an ever evolving topic area so I'm sure the paper will change over time, but here's the version for now:

This is very much based on our experience of tuning real ADF Enterprise Applications, for both internal and external customers.  Hopefully there are a few useful nuggets of information in there for you.

Thursday Dec 08, 2011

Automating Remote Deployment to Oracle WebLogic from Hudson

As part of my effort to streamline the continuous build processes around my sample and prototype portfolio, I use Hudson to trigger new builds of each sample as it is updated. Thus far I had been getting as far as testing, building (using OJDeploy), precompiling and archiving, but I'm not gotten around to deploying as part of those jobs. 

The main reason for this was due to the fact that the Hudson deployer plugin does not support WebLogic as a target (this is due in turn to a limitation of the underlying Cargo CodeHaus project) . Anyway I digress.

So rather than using the deploy step in a Hudson FreeStyle project, we need to add a normal build step at the end of the chain to handle the WebLogic deployment. Now as it happens, this is pretty easy to do, and given that you'll generally be building ADF projects with OJDeploy in any case, you have everything to hand, and no real additional setup is required. If you're not building and deploying an ADF project then the additional step you will need here is to ensure that there is a WebLogic server installation available on the Hudson machine that is executing the job.

The actual command line to trigger the deploy is pretty simple, however, at it's most basic level it takes a plain-text username and password for the WebLogic administrator.  This is probably not the kind of information that you want to leave sprinkled throughout your job configurations and console logs, so preventing that is the first step.

(Note: Even so I'm running in a pretty simple dev/test environment where the WebLogic is within the same network and I'm not using SSL, if you read the documentation references below you'll see how to beef this up for a more secure environment.)

Step 1. Conceal your Identity

The utility that we'll be using to do the deploy is weblogic.Deployer (more later, but see here for the 10.3.5 doc on this).  As I mentioned, that takes username and password parameters which we don't want, however, it also provides a way to externalize the credentials and that's what I'll use here.  This is a step that you need to carry out on the Hudson executor machine and it takes place outside of Hudson all together. We'll use the weblogic.WLST utility to generate an encrypted credential store to hold the username and password for the remote server I'm about to deploy to. You will need WebLogic or JDeveloper installed on the machine to get access to the  weblogic.Deploy and weblogic.WLST commands.  I'm signifying that location with ${WLS_HOME} here. For the sake of illustration I'm logged in as the user that runs the Hudson process and I'll store the new credential files in a sub-directory of that users home directory called wlskeys. I'm using the bash shell here:

source ${WLS_HOME}/wlserver_10.3/server/bin/setWLSEnv.sh
java weblogic.WLST

Initializing WebLogic Scripting Tool (WLST) ...
Welcome to WebLogic Server Administration Scripting Shell
Type help() for help on available commands

wls:/offline> connect('weblogic','secret_admin_password',
't3://duncan_wls:7001')

Connecting to t3://duncan_wls:7001 with userid weblogic ...
Successfully connected to Admin Server 'AdminServer' that belongs to domain 'adf_domain'.
Warning: An insecure protocol was used to connect to the server. To ensure on-the-wire security, the SSL port or Admin port should be used instead.

wls:/adf_domain/serverConfig> storeUserConfig('~/wlskeys/duncan_wls_userConfig.properties',
 '~/wlskeys/duncan_wls_userConfig.key')

Creating the key file can reduce the security of your system if it is not kept in a secured location after it is created.
Do you want to create the key file? y or n
y
The username and password that were used for this WebLogic Server connection are stored in /home/hudson/wlskeys/duncan_wls_userConfig.properties and /home/hudson/wlskeys/duncan_wls_userConfig.key.

wls:/adf_domain/serverConfig> exit()
Exiting WebLogic Scripting Tool.

So there you have it. If you now peek in the specified directory you will be able to see the generated files.  It goes without saying that the files in question are ones to protect! You can go ahead and generate as many of these file pairs as you have servers for Hudson to deploy to.

Step 2. The Deploy command from Hudson 

The actual deploy step from Husdon is defined as a Execute Shell step in your job. To cut down on typing and allow for different directory structures across the Hudson slaves I'm using,  I've predefined some variables in the general Hudson config and overridden them appropriately on the slave node configuration (Node Properties -> Environment Variables):

  • ${WLS_HOME} WebLogic Home directory
  • ${WLS_KEYSTORE} The directory on which I save the properties and key files generated in Step 1 
  • ${TESTING_SERVER} The URL for the Admin Server (e.g. t3://duncan_wls:7001)

 Then the Hudson build step looks looks this:

source ${WLS_HOME}/wlserver_10.3/server/bin/setWLSEnv.sh
java weblogic.Deployer -adminurl ${TESTING_SERVER}
  -userconfigfile ${WLS_KEYSTORE}/duncan_wls_userConfig.properties
  -userkeyfile ${
WLS_KEYSTORE}/duncan_wls_userConfig.key
  -deploy ${WORKSPACE}/deploy/myapp.ear -upload

The -upload parameter is the important one here, that will take the EAR file that Hudson has build and transfer it to the remote machine before trying the install.

Variations on the Theme

This approach is, of course, just one way of achieving this. Other options would include using the FTP-Publisher plugin to push the file to the remote server, and if this was set up to the right place and WebLogic was auto-deploying in development mode you could bypass the need for the WebLogic install on the Hudson executors. However, given that for an ADF application we really need to use OJDeploy to build the app we'll have this stuff to hand anyway. 

Other variations would include using WLST or the WebLogic ANT tasks rather than weblogic.Deploy. The end result and approach are identical in these cases.

I'm sure that the WebLogic plugin for Maven is probably an even better route to go for this entire process, however, I've not Maven-ised my main codebase yet so that will have to wait for another day.

Monday Nov 28, 2011

Zip files for TUHRA2 Re-published

Just a quick note to all readers of the venerable Oracle JDeveloper 11g Handbook: A Guide to Fusion Web Development. The chapter ZIP files and scripts have all now been re-located to java.net in the tuhra2 project: http://java.net/projects/tuhra2.

Thursday Nov 24, 2011

JSP Precompilation for ADF Applications

A question that comes up from time to time, particularly in relation to build automation, is how to best pre-compile the .jspx and .jsff files in an ADF application. Thus ensuring that the app is ready to run as soon as it's installed into WebLogic. In the normal run of things, the first poor soul to hit a page pays the price and has to wait a little whilst the JSP is compiled into a servlet. Everyone else subsequently gets a free lunch. So it's a reasonable thing to want to do...

Let Me List the Ways

So forth to Google (other search engines are available)... which lead me to a fairly old article on WLDJ - Removing Performance Bottlenecks Through JSP Precompilation. Technololgy wise, it's somewhat out of date, but the one good point that it made is that it's really not very useful to try and use the precompile option in the weblogic.xml file. That's a really good observation - particularly if you're trying to integrate a pre-compile step into a Hudson Continuous Integration process. That same article mentioned an alternative approach for programmatic pre-compilation using weblogic.jspc. This seemed like a much more useful approach for a CI environment. However, weblogic.jspc is now obsoleted by weblogic.appc so we'll use that instead.  Thanks to Steve for the pointer there.

And So To APPC

APPC has documentation - always a great place to start, and supports usage both from Ant via the wlappc task and from the command line using the weblogic.appc command. In my testing I took the latter approach.

Usage, as the documentation will show you, is superficially pretty simple.  The nice thing here, is that you can pass an existing EAR file (generated of course using OJDeploy) and that EAR will be updated in place with the freshly compiled servlet classes created from the JSPs. Appc takes care of all the unpacking, compiling and re-packing of the EAR for you. Neat. 

So we're done right...? Not quite.

The Devil is in the Detail

 OK so I'm being overly dramatic but it's not all plain sailing, so here's a short guide to using weblogic.appc to compile a simple ADF application without pain. 

Information You'll Need

The following is based on the assumption that you have a stand-alone WLS install with the Application Development  Runtime installed and a suitable ADF enabled domain created. This could of course all be run off of a JDeveloper install as well

1. Your Weblogic home directory. Everything you need is relative to this so make a note.  In my case it's c:\builds\wls_ps4.

2. Next deploy your EAR as normal and have a peek inside it using your favourite zip management tool. First of all look at the weblogic-application.xml inside the EAR /META-INF directory. Have a look for any library references. Something like this:

<library-ref>
   <library-name>adf.oracle.domain</library-name>
</library-ref> 

 Make a note of the library ref (adf.oracle.domain in this case) , you'll need that in a second.

3. Next open the nested WAR file within the EAR and then have a peek inside the weblogic.xml file in the /WEB-INF directory. Again  make a note of the library references.

4. Now start the WebLogic as per normal and run the WebLogic console app (e.g. http://localhost:7001/console). In the Domain Structure navigator, select Deployments.

5. For each of the libraries you noted down drill into the library definition and make a note of the .war, .ear or .jar that defines the library. For example, in my case adf.oracle.domain maps to "C:\ builds\ WLS_PS4\ oracle_common\ modules\ oracle. adf. model_11. 1. 1\ adf. oracle. domain. ear". Note the extra spaces that are salted throughout this string as it is displayed in the console - just to make it annoying, you'll have to strip these out.

6. Finally you'll need the location of the adfsharebean.jar. We need to pass this on the classpath for APPC so that the ADFConfigLifeCycleCallBack listener can be found. In a more complex app of your own you may need additional classpath entries as well. 

Now we're ready to go, and it's a simple matter of applying the information we have gathered into the relevant command line arguments for the utility

A Simple CMD File to Run APPC 

Here's the stub .cmd file I'm using on Windows to run this.

@echo off
REM Stub weblogic.appc Runner
setlocal

set WLS_HOME=C:\builds\WLS_PS4
set ADF_LIB_ROOT=%WLS_HOME%\oracle_common\modules
set COMMON_LIB_ROOT=%WLS_HOME%\wlserver_10.3\common\deployable-libraries
set ADF_WEBAPP=%ADF_LIB_ROOT%\oracle.adf.view_11.1.1\adf.oracle.domain.webapp.war
set ADF_DOMAIN=%ADF_LIB_ROOT%\oracle.adf.model_11.1.1\adf.oracle.domain.ear
set JSTL=%COMMON_LIB_ROOT%\jstl-1.2.war
set JSF=%COMMON_LIB_ROOT%\jsf-1.2.war
set ADF_SHARE=%ADF_LIB_ROOT%\oracle.adf.share_11.1.1\adfsharembean.jar

REM Set up the WebLogic Environment so appc can be found
call %WLS_HOME%\wlserver_10.3\server\bin\setWLSEnv.cmd
CLS

REM Now compile away!
java weblogic.appc -verbose -library %ADF_WEBAPP%,%ADF_DOMAIN%,%JSTL%,%JSF% -classpath %ADF_SHARE% %1

endlocal

Running the above on a target ADF .ear  file will zip through and create all of the relevant compiled classes inside your nested .war file in the \WEB-INF\classes\jsp_servlet\ directory (but don't take my word for it, run it and take a look!)

And So...

In the immortal words of  the Pet Shop Boys, Was It Worth It? Well, here's where you'll have to do your own testing. In  my case here, with a simple ADF application, pre-compilation shaved an non-scientific "3 Elephants" off of the initial page load time for the first access of each page. That's a pretty significant payback for such a simple step to add into your CI process, so why not give it a go.

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