Wednesday Oct 15, 2014

Using a Translatable String as the Default Value for a Bind Variable

I've just been looking at a scenario where we needed to populate an ADFBC query bind variable using a predefined value. Now that in itself is not difficult - the bind variable definition takes a default value exactly for this, however, in this case the value supplied as the default needed to be read from the locale specific resource bundle.

I wondered here if it would be possible to convert the default value for the bind to be an expression rather than a literal and use groovy to derive the translated string?

Well it took a little bit of time to come up with the correct path, but indeed it turns out to be possible with one small constraint about the keys in the bundle that have to be used.

So to start off, here's the groovy expression I use in the default value for the bind variable: 

source.viewObject.getProperty('TRANSLATABLE_BINDVAR')

That expression refers to a translatable property called TRANSLATABLE_BINDVAR defined in the relevant View Object as a custom property (General finger tab, Custom Properties section). this, in turn, maps to a resource in the bundle with a key generated from the full name of the View Object + property name + "_VALUE". e.g. in this case:

oracle.adf.demo.model.ValueFromBIndVO.TRANSLATABLE_BINDVAR_VALUE

This is the resource that you can then translate and the value of which will be used at runtime.

An Explanation

 So what does this all mean? Unfortunately there is no way (that I've found) in groovy to reach directly into the ui hints of the bind variable itself If you start the expression with adf.object to start from the variable itself you can't really get anywhere.  So instead, we have to walk up the component hierarchy to the View Object that owns the bind variable and then get the resource from there. Just asking for the value of the custom TRANSLATABLE_BINDVAR property.  By using a translatable custom property for this, the developer has an existing UI to use that will create the resource keys for them. Furthermore, because we use the getProperty() API on the View Object  as an indirect way of getting the value, rather than using ResourceBundle APIs, we don't have to worry about finding out what locale to use - that's all handled automatically.

Thursday Oct 09, 2014

Showing Initial Selection in Your ListView

If you use a ListView or a Table component which enables selection by the user in a master detail view you may feel that there is a slight inconsistency of behaviour. When the user first enters the screen it may, by default, look something like this (in this example a ListView to the left and the "selected" department detail to the right in a form layout):

ListView showing no initial selection

Notice that the associated detail form is showing the first row in the collection as you would expect, but visually, the corresponding row in the ListView is not visually highlighted. Compare that with what happens when the user clicks on a row in the ListView to select another row:

ListView with a selection made by the user

Now the selected row in the listView is highlighted with the skin's selection colour.

So the question is, how can we show the page initially in a manner consistent with this?
e.g.

Corrected initial selection

Well in fact the binding layer already has everything you need and it's a trivial thing to fix. All we have to do here is to set the selectedRowKeys attribute of the listView tag. So, assuming we have a listView showing departments the tag would look something like this:

<af:listView value="#{bindings.DepartmentsView1.collectionModel}" 
             var="item"
             selectedRowKeys="#{bindings.DepartmentsView1.collectionModel.selectedRow}" 
             selection="single" 
             selectionListener="#{bindings.DepartmentsView1.collectionModel.makeCurrent}"
             emptyText="#{bindings.DepartmentsView1.viewable ? 'No data to display.' : 'Access Denied.'}"
             fetchSize="#{bindings.DepartmentsView1.rangeSize}" 
             id="lv1">

Doing It From Scratch

So this is an example where the binding layer does everything for you, great! However, what wanted to do this manually? I'm showing you this because it happens to illustrate a few useful techniques and code snippets.  In general I'd stick with the simple approach above though! 

Setting this up requires some simple steps and a small amount of code, so let's go.

Step 1: Defining the Selected Row Keys

The core mechanism of this solution is to pre-seed the selectedRowKeys property of the listView component with the row key of whichever row is current in the underlying collection.  So the first step is to define somewhere to hold that row key set. So we start by defining a ViewScope managed bean (called lvState in this example) and within that, we just define a variable to hold a RowKeySet reference.

So we start with the Java class:

package oracle.adf.demo;
import org.apache.myfaces.trinidad.model.RowKeySet;
public class ListViewState {
  RowKeySet _selectedRowKeys;

  public void setSelectedRowKeys(RowKeySet rks) 
  {
    this._selectedRowKeys; = rks;
  }

  public RowKeySet getSelectedRowKeys() 
  {
    return _selectedRowKeys;
  }
}

And define that as a managed bean in the relevant task flow definition:

<managed-bean>
   <managed-bean-name>lvState</managed-bean-name>
   <managed-bean-class>oracle.adf.demo.ListViewState</managed-bean-class>
   <managed-bean-scope>view</managed-bean-scope>
</managed-bean>

Once that's defined you can update your <af:listView> tag to reference it:

<af:listView value="#{bindings.DepartmentsView1.collectionModel}" 
             var="item"
             selectedRowKeys="#{viewScope.lvState.selectedRowKeys}" 
             selection="single" 
             selectionListener="#{bindings.DepartmentsView1.collectionModel.makeCurrent}"
             emptyText="#{bindings.DepartmentsView1.viewable ? 'No data to display.' : 'Access Denied.'}"
             fetchSize="#{bindings.DepartmentsView1.rangeSize}"
             id="lv1"> 

Step 2: Pre-Seeding the Row Key Set 

As defined, the row key set picked up by the listView will of course be null until the user physically makes a selection - we're no better off than before. So we now have to work out a way of taking the current selection from the model / binding layer, obtaining it's key and then setting up the row key set before the listView is rendered.

The simplest way of doing this is to utilise the capability of JSF 2 to simply define events to be executed as the page is being set up for rendering. In this case we use the postAddToView event inside of the listView to execute the setup code:

<af:listView ...>
  <f:event type="postAddToView" listener="#{lvEvents.setupListViewSelection}"/>
  <af:listItem ...>

The event tag points at a request scope managed bean called lvEvents.  This bean needs access to the lvState managed bean that we defined in the first step:

<managed-bean>
  <managed-bean-name>lvEvents</managed-bean-name>
  <managed-bean-class>oracle.adf.demo.ListViewEventManager</managed-bean-class>
  <managed-bean-scope>view</managed-bean-scope>
  <managed-property>
    <property-name>state</property-name>
    <property-class>oracle.adf.demo.ListViewState</property-class>
    <value>#{lvState}</value>
  </managed-property>
</managed-bean>

The ListViewEventManager class should therefore have a variable to hold the state with the appropriate getter and setter:

public class ListViewEventManager {
    ListViewState _state;
    public void setState(ListViewState _state) {
        this._state = _state;
    }
    public ListViewState getState() {
        return _state;
    }
    ...

Finally, the implementation of the listener defined in the <f:event> tag needs to be added to the class:

public void setupListViewSelection(ComponentSystemEvent componentSystemEvent) {
  if (getState().getRks() == null) {
     BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();
     DCIteratorBinding iterBind =
                ((DCBindingContainer)bindings).findIteratorBinding("DepartmentsView1Iterator");
            Key key = iterBind.getCurrentRow().getKey();
            RowKeySet rks = new RowKeySetImpl();
            rks.add(Collections.singletonList(key));
            getState().setRks(rks);
        }
}    

So now, when the page renders, if the selection row key set has not been setup due to user interaction we will go ahead and create it, seeding the rowkey of whatever row the model considers to be current.

Friday Aug 15, 2014

Ensuring High Availability in ADF Task Flows

Just a quick article today on ADF Controller Scopes and specifically ensuring that your application is correctly propagating state stored in PageFlow and View Scope across the cluster. This information can be found in the product doc and in Jobinesh Purushothaman's excellent book (Chapter 12 - Ensuring High Availability), however, more references means more eyes and fewer mistakes!  

Some Background

When you store state in a managed bean scope how long does it live and where does it live?  Well hopefully you already know the basic answers here, and for scopes such as Session and Request we're just dealing with very standard stuff. One thing that might be less obvious though, is how PageFlow and View Scope are handled.  Now these scopes persist (generally) for more than one request, so there is obviously the possibility that you might get a fail-over between two of those requests.  A Java EE server of whatever flavour doesn't know anything about these extra ADF memory scopes so it can't be automatically managing the propagation of their contents can it?  Well the answer is yes and no.  These "scopes" that we reference from the ADF world are ultimately stored on the Session (albeit with a managed lifetime by the framework), so you'd think that everything should be OK and no further work is going to be needed to ensure that any state in these scopes is propagated - right?  Well no, not quite, it turns out that several key tasks are often missed out. So let's look at those.

First of All -  Vanilla Session Replication

Assuming that WebLogic is all configured, this bit at least is all automatic right? Well no. In order to "know" that an object in the session needs to be replicated WebLogic relies on the HttpSession.setAttribute() API being used to put it onto the session. Now if you instanciate a managed bean in SessionScope through standard JSF mechanisms then this will be done and you're golden.  Likewise if you grab the Faces ExternalContext and grab the Session throught that (e.g. using the getSession() API), then call the setAttribute() API on HttpSession, you've correctly informed WebLogic of the new object to propagate.

You might already see though, that there is a potential problem in the case where the object stored in the session is a bean and you're changing one of its properties. Just calling an attribute setter on an object stored on the session will not be a sufficient trigger to have that updated object re-propagated, so the version of the object elsewhere will be stale.  So when you update a bean on the session in this way, and want to ensure that the change is propagated, then re-call the setAttribute() API.   
Got it? OK, on to the ADF scopes:

Five Steps to Success For the ADF Scopes 

The View and PageFlow scopes are, as I mentioned, ultimately stored on the session.  Just as in the case of any other object stored in that way,  changing an internal detail of those representaive objects would not trigger replication. So, we need some extra steps and of course we need to observe some key design principles whilst we're at it:

  1. Observe the UI Manager Pattern and only store state in View and PageFlow scope that is actually needed and is allowed (see 2)
  2. As for any replicatable Session scoped bean, any bean in View or PageFlow scope must be serializable (there are audits in JDeveloper to gently remind you of this).
  3. Only mark for storage that which cannot be re-constructed. Again a general principle; we wish to replicate as little as possible, so use the transient marker in your beans to exclude anything that you could possibly reconstruct over on the other side (so to speak). 
  4. In the setters of any attributes in these beans (that are not transient) call the ControllerContext markScopeDirty(scope) API. e.g. ControllerContext.getInstance().markScopeDirty(AdfFacesContext.getCurrentInstance().getViewScope());   This does the actual work of making sure that the server knows to refresh this state across the cluster
  5. Finally, set the HA flag for the controller scopes in the .adf/META-INF/adf-config file. This corresponds to the following section inside of the file:
<adf-controller-config xmlns="http://xmlns.oracle.com/adf/controller/config">
  <adf-scope-ha-support>true</adf-scope-ha-support>
</adf-controller-config> 
If this flag is not set, the aforementioned markScopeDirty() API will be a no-op. So this flag provides a master switch to throw when you need HA support and to avoid the cost when you do not. 

So if you've not done so already, take a moment to review your managed beans and check that you are really all doing this correctly. Even if you don't need to support HA today you might tomorrow... 

Wednesday Aug 13, 2014

Maven and ADFBC Unit Tests in 12.1.3

An issue that has come up recently has revolved around setting your Maven POM up in 12.1.3 such that you can run ADF BC JUnit Tests successfully both interactively in the IDE and headless through Maven maybe in your Hudson Jobs.  Out of the box, the default POM that you will end up with will be missing a couple of vital bits of information and need a little extra configuration.

Here are the steps you'll need to take:

Step 1: Use The Correct JUnit Dependency

Once you have created some unit tests JDeveloper will have added dependencies for JUnit from the JDeveloper JUnit extensions, something like this:

<dependency>
  <groupId>com.oracle.adf.library</groupId>
  <artifactId>JUnit-4-Runtime</artifactId>
  <version>12.1.3-0-0</version>
  <type>pom</type>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>com.oracle.adf.library</groupId>
  <artifactId>JUnit-Runtime</artifactId>
  <version>12.1.3-0-0</version>
  <type>pom</type>
  <scope>test</scope>
</dependency> 

Delete both of these entries, if they exist, and drop in a dependency to the vanilla JUnit 4.11 library instead:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <type>jar</type>
  <scope>test</scope>
</dependency> 

Failing to make this change will result in the following error:

 java.lang.NoClassDefFoundError: org/junit/runner/notification/RunListener 

Step 2: Configure the Surefire Plugin to Select the Correct JUnit Version

This is probably not needed but it ensures that Surefire is left in no doubt about what version of JUnit it shoudl be working with (in this case a version of 4.7 or higher):

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.17</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven.surefire</groupId>
      <artifactId>surefire-junit47</artifactId>
      <version>2.17</version>
    </dependency>
  </dependencies>
</plugin>

Step 3 : Identifying Your connections.xml File 

When running outside of JDeveloper we need to set things up so that  the unit tests can actually find the connection information that defines the datasource that your Application Modules are using.  To do this, we need to add a configuration section to the  Surefire plugin to add the location into the classpath.  Add the collowing configuration into the Surefire plugin, after the <dependencies>  section:

<configuration>
  <additionalClasspathElements>
    <additionalClasspathElement>
      ${basedir}/../.adf
    </additionalClasspathElement>
  </additionalClasspathElements>
</configuration> 

This will ensure that the connection information can be found.  If you forget this step you'll get a stack trace including the message:

MDS-00013: no metadata found for metadata object "/META-INF/connections.xml" 

Step 4 - Supply the Missing JPS Library

Finally we need to supply the location of one extra required library.  This requirement will hopefully be resolved in the next release, but for now add it.  Again this is added to the Surefire plugin configuration <additionalClasspathElements> 

<additionalClasspathElement>
  ${oracleHome}/oracle_common/modules/oracle.jps_12.1.3/jps-manifest.jar
</additionalClasspathElement> 

Omitting this will result in the error:

WARNING: No credential could be loaded for Reference = Reference Class Name: oracle.jdeveloper.db.adapter.DatabaseProvider

For reference here's the complete Surefire plugin definition 

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.17</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven.surefire</groupId>
      <artifactId>surefire-junit47</artifactId>
      <version>2.17</version>
    </dependency>
  </dependencies>
  <configuration>
    <additionalClasspathElements>
      <additionalClasspathElement>
        ${basedir}/../.adf
      </additionalClasspathElement>
      <additionalClasspathElement>
        ${oracleHome}/oracle_common/modules/oracle.jps_12.1.3/jps-manifest.jar
      </additionalClasspathElement>
    </additionalClasspathElements>
  </configuration>
</plugin> 

Friday Jul 25, 2014

Per-Component Instance Skinning

A question that comes up from time to time happened to be raised twice three times this week, so I though it would be good to write the answer down.  The question is this; How can I override the visual properties of a single instance of a component? 

Now in many cases you can simply use inlineStyle or, (better) assign a styleClass to the component instance and associate a CSS file with the page using <af:resource>.  This will work in the simple case where, for example, it's a basic component such as an <af:spacer> or <af:panelGroupLayout layout="vertical">.  However, in the case of a more complex component you may not get the fidelity through inlineStyle, styleClass or even contentStyle. 

In these cases you will need to extend the skin to create a instance selector for the component type.  This will allow you to change any skinning slector, but rather than doing so for every instance of the component, you will end up with a sub-type that can be applied to just those instances that require it.

All you have to do here is to extend the skin as per normal (just create a new skin in 12.1.3 and all the metadata will be set up for you in the trinidad-skins.xml file). In the skin you define a custom style class and then append the selector that you want to change.  So for example, here's a custom version of one of the commandMenuItem selectors that sets a specific background:

.saveMenu af|commandMenuItem::menu-item-icon-style {
    background-image:url("images/save.png");
    background-repeat:no-repeat;
} 

And that's then applied to the menu item in question:

 
<af:commandmenuitem text="save" styleclass="saveMenu">

That's it, pretty simple but can be useful

Thursday Jun 26, 2014

Diagram at Last

This blog has been quiet for a while - not because I've abandonded it, but rather, because of what I've been working on.  Now that the 12.1.3 release of JDeveloper / ADF is available I can open up again, what a relief! And what is it I've been hacking on? Diagrams!. Yes, this is just the absolutely coolest (in my opinion) new feature of the 12.1.3 release of ADF - the  new DVT diagram component. It's a component that is so powerful and flexible in terms of the way that data can be represented and interacted with, that it's slightly too much to cover in a single Blog entry.  In fact, by my estimation it will take about 10 articles to cover the essentials of diagram and so that's exactly what I'll be doing.  However, those articles will not be here on the Groundside blog, but instead they'll be over on the new Data Visualization blog, but I'll publish an pointer index here as well.  I've just published the introduction to get you started and over the next week you'll have a daily injection of diagram goodness to build up your knowledge. So add the DVT blog to your RSS feed and sit back for the ride. 

Wednesday Feb 26, 2014

Customizing the Axis Labels in ADF Graphs

The various data visualization (DVT) graphs provided as part of the ADF Faces component set provide a very rich and comprehensive set of visualizations for representing your data.  One of the issues with them, that some folks struggle with however, is the fact that not all the features are controlled in an a completely declarative manner. 

In this article I want to concentrate on labeling capabilities for a graph axis, looking first of all that the declarative approaches, but then following that up with the more advanced programmatic option. 

Managing Labels Declaratively 

Control over the labels on the axis tick points is a good example of making the simple things declarative and the advanced things possible.  For basic numeric formatting you can do everything with tags - for example formatting as currency, percentage or with a certain precision.    

This is a default (bar)graph plotting employee salary against name, notice how the Y1 Axis has defaulted to a fairly sensible representation of the salary data using 0-14K:


I can change that default scaling by setting the scaling attribute in the <dvt:y1TickLabel> tag. This allows scaling at the level of none | thousand | million | billion | trillion | quadrillion (enough to show national debt then!):

<dvt:y1TickLabel id="y1TickLabel1" scaling="none"/>

Changes the graph to:


We can then further change the pattern of the numbers themselves by embedding <af:convertNumber> inside of the <dvt:y1TickLabel> tag.

e.g.

<dvt:y1TickLabel id="y1TickLabel1" scaling="none">
  <af:convertNumber type="currency" currencyCode="USD"/>
</dvt:y1TickLabel> 

Adds currency formatting:

And using the <dvt:graphFont> we can change colors and style:

<dvt:y1TickLabel id="y1TickLabel1" scaling="none">
  <dvt:graphFont name="SansSerif" size="8" color="#FF0000" bold="true" italic="true" />
  <af:convertNumber type="currency" currencyCode="USD"/>
</dvt:y1TickLabel> 

Giving:


Need More Control?  Using the TickLabelCallback...

So we can achieve quite a lot by simply using the tags.  However, what about a more complex requirement such as replacing a numerical value on an axis with a totally different string e.g. converting to a Roman Numeral (I, IV XII etc.) or maybe converting a millisecond value to a formatted date string?  To do this, ADF provides a simple callback that you can implement to map a value to whatever string you need.  Here's a simple case where I've plotted the salaries in department 100 of the standard HR EMPLOYEES demo table against the HireDate on a scatter plot.  For the sake of illustration I've actually converted the HireDate to it's time value (e.g. a long value representing the number of milliseconds since 01/01/1970) .  In a real graph I'd use the proper support that we have for representing time and date and just map a date object. Then you get to use the timeSelector and can directly set the formatting, however, bear with me because I'm just illustrating the point here.

Here's the default output with the millisecond version of the date, as you can see the millisecond value gets automatically scaled to the billions level.

To override the default representation of the millisecond value we will need to create a java class that implements the oracle.dss.graph.TickLabelCallback interface.  Here's the simple example I'll use in this case:

import java.text.SimpleDateFormat;
import oracle.dss.graph.DataTickLabelInfo;
import oracle.dss.graph.GraphConstants;
import oracle.dss.graph.TickLabelCallback;
import oracle.dss.graph.TickLabelInfo;

public class MSToDateFormatLabelCallback implements TickLabelCallback, Serializable {

  @Override
  public String getTickLabel(TickLabelInfo tickLabelInfo, int axisID) {
    String label = null;
    if (axisID == GraphConstants.X1TICKLABEL) {
      long timeInMillis = (long) ((DataTickLabelInfo) tickLabelInfo).getValue();
      SimpleDateFormat fmt = new SimpleDateFormat("MM/yy");
      label = fmt.format(timeInMillis);
    } else {
      label = "" + ((DataTickLabelInfo) tickLabelInfo).getValue();
    }
    return label;
  }
} 

As you can see the formatting  is applied only to the specified axis and we have to upcast the tickLabelInfo argument to a DataTickLabelInfo in order to gain access to the value that is being applied. Note that the callback class must also be Serializable. 

Once you have this class, then you need to apply it to the chart instance by calling the relevant set*TickLabelCallback. So for example I might set this in the backing bean for a page in the setter used by the binding attribute on the dvt graph component

public class GraphPageHandler {
    private UIGraph scatterPlot;
    public void setScatterPlot(UIGraph scatterPlot) {
        this.scatterPlot = scatterPlot;
        scatterPlot.setX1TickLabelCallback(new MSToDateFormatLabelCallback());
    } 

Now with the callback in place and the addition of:

  1. Using  the axisMinValue attribute along with axisMinAutoScaled="false" on the <dvt:x1Axis>  to reset the start date for the plot to the year 2000 rather than the default 1970
  2. Some of the currency formatting that we've already looked at to format the Y axis

Here's the result:

Monday Feb 24, 2014

Programmatic UI Scrolling in ADF Faces

I've been dealing with a use case recently where we needed to synchronize two panels in a user interface, where (in the simple version) selecting an object on one side of the screen would also select a corresponding object in a scrolling area on the other side of the screen and scroll in into view.  the scrolling panel in this case being a panelGroupLayout / scroll, not a table.

Declarative  scrolling is really simple, you can drop a UI element such as commandButton onto the page and then nest, within that, the behavior tag <af:scrollComponentIntoViewBehavior>. This tag is great if you have a long page and want to provide the users with a way to navigate to different sections, however, it needs to be wired to a command component and the user needs to physically invoke the operation.

In this case, everything needed to happen in reaction to a series of complex programmatic events and so, the use of the behavior tag did not cut it.  So I did a small amount of testing to see if it was possible to achieve the same result in a hands off fashion from within java code.

It turns out to be pretty simple to do, but there are different approaches depending on your version:

Programatic Scrolling in 11.1.2.n and 12c

In the newer versions of ADF this issue is addressed head on with a new API on the AdfFacesContext -> scrollComponentIntoView(component, focus);  This API takes a UIComponent reference for the component to scroll to, and the second argument is a boolean flag to indicate if the component should take focus as well (for example if it was an input field).  

Programatic Scrolling in 11.1.1.n

 Prior to ADF Faces 11.1.2 the handy  scrollComponentIntoView() api does not exist, however, we can acheive the same effect using some JavaScript. (Note if you are on a version of ADF that does support the scrollComponentIntoView() Java API then use that, not this.)

As with the behavior tag, you need to do two things.

  1. The target component has to have clientComponent="true" as this scrolling operation all takes place on the client
  2. You need the full id of the target component.  This should include any naming containers such as templates and regions.  The safest way to do this is to call getClientId() on the java component itself. 

 Then you can just invoke this little function.  Note that the code does not have any error handling if you get the component ID wrong so feel free to improve it to make it more robust:

import org.apache.myfaces.trinidad.render.ExtendedRenderKitService;
import org.apache.myfaces.trinidad.util.Service; 

private void scrollIntoView(String componentId){
  StringBuilder builder = new StringBuilder();
  builder.append("var c = AdfPage.PAGE.findComponent('");
  builder.append(componentId);
  builder.append("'); c.scrollIntoView(true,null);");
        
  FacesContext fctx = FacesContext.getCurrentInstance();
  ExtendedRenderKitService erks =
    Service.getRenderKitService(fctx, ExtendedRenderKitService.class);
  erks.addScript(fctx, builder.toString());        
} 

If you use this function an it appears not to work you may be hitting a timing issue where the framework is, in turn, resetting the focus after you have set it.  If that seems to be happening in your case, then the solution is to re-write the above JavaScript slightly to execute the scrollIntoView() after a short delay.  You can use a JavaScript  setTimeout() function on the window object to do this.  Delaying the scroll call for about 10ms should be sufficient.

Tuesday Dec 03, 2013

Click History in ADF 12c

In previous posts (see here) I've covered various aspects of logging in ADF and in conjunction with a presentation delivered at the UK Oracle Users Group, I'm continuing the trend with an article on the new Click History functionality within 12c. 

So What is Click History? 

Basically the feature relates to instrumentation that is built into the ADF framework which, when switched on, continuously reports the actions that users have taken within the UI.  This mostly relates to physical clicks on UI elements such as buttons and tabs, but it also reports scrolling events, graph draws and more.  Why is it useful?  Well the point is that it can help answer that particular question - "What did you do before you got the error?" You can see how you could leverage this in a feedback or error reporting function in a UI where the information is extracted and automatically bundled with other useful information for the Support staff to have a look at. 

Each Click History record provides information about the  component, region and view that reported the event, along with key information about the application,  the user and the DSID (Diagnostic Session ID).

Here's a screen shot from some in-application diagnostics that I've developed as an small example.  In this case the history is being dumped and read into the actual application UI.  This is something I'll cover doing in a follow up article. 

Click History Data

Enabling Click History 

As I mentioned Click History is part of the framework in 12c, however, it's not switched on for every application by default.  There will be a performance overhead for any kind of tracing like this so we want to be explicit about choosing to use it. 

In your application itself you need to make 2 small changes:

  1. Create a new web.xml context parameter with the name oracle.adf.view.faces.context.ENABLE_ADF_EXECUTION_CONTEXT_PROVIDER and set the value to true.
  2. Add an extra library reference to your /WEB-INF/ weblogic.xml file (create this if you don't have one already).  Here's the reference you need:

     <library-ref>
       <library-name>odl.clickhistory.webapp</library-name>
     </library-ref> 

Assuming that you have a correctly configured Weblogic domain that has been extended with JRF this library will already be defined for you.  

So in your application, that's basically it. However, we then need to do a little work on the server to configure the server side parameters for this, specifically how much space should be devoted to the Click History buffer.  To explain, Click History has a logical circular buffer of a specified size. As the buffer fills up then new records will be written from the start again and the older records discarded. When you want to look at the history, then you have to dump this buffer, but we'll come to that later. 

Server Side Configuration of Click History 

Switching Click History On or Off

 Although individual applications enable Click History, the server also has to be told to pay attention.  This is very simple to do as it is controlled by switching a particular logger (oracle.clickhistory.EUM) to the NOTIFICATION level. This can be done from Enterprise Manager, or from the Weblogic scripting tool, WLST. Here, I'll show you how to do it from the command line using WLST, I think you can probably work out how to do it though EM yourself. 

So, with WLST running:

wlst> connect()
wlst> getLogLevel(logger='oracle.clickhistory.EUM')
INCIDENT_ERROR:1

The value of INCIDENT_ERROR indicates that the facility is OFF, so we can set it on with the following command:

wlst> setLogLevel(logger="oracle.clickhistory.EUM",
                  target="AdminServer",level="INFO", 
                  persist="1")

If you've not encountered the setLogLevel command in WLST before, just issue help('setLogLevel') from the WLST command line to view the options and what they mean. 

Viewing the Click History Buffer

Unlike a normal logging message we can't view the Click History output directly in EM, instead we need to explicitly dump the buffer to the console, or more usually a file.  As I mentioned we can also do this in code which is something I'll cover in another article. So for now, let's look at how to do that from WLST:

wlst>executeDump(name="odl.quicktrace", 
                 outputFile="/home/oracle/ch_dump.out")

Again you can issue the help() command in WLST for more information about the options on executeDump(), but in this case I'm just taking the simple option of dumping the data to a file.. Inside the file you'll see the output containing all of the lines of  Click History information like this:

[2013-11-20T09:08:53.332+00:00] [AdminServer] [NOTIFICATION] [] [oracle.clickhistory.EUM] 
[tid: 120][userId: alice] [ecid: 67ea61aa-dde8-4e35-afa2-a75a24c8a820-00001769,0] 
[CLICK_COMPTYPE: oracle.adf.RichShowDetailItem] [APP: SummitProductionDeploy] 
[DSID: 0000K9ohwuNFw000jzwkno1IYTfR00000X] [CLICK_VIEWID: /index] 
[CLICK_REGIONVIEWID: /customer-task-flow-definition/Customers] [CLICK_STARTTIME: 1384938532808] 
[CLICK_RENDERTYPE: oracle.adf.rich.Item] [CLICK_TYPE: disclosure] 
[CLICK_COMPCLIENTID: pt1:r1:0:sdi2] [CLICK_WINDOWID: w0] [CLICK_REGIONVIEWNAME: ] 
[CLICK_COMPDISPLAYNAME: Orders Dashboard] 

 It's pretty easy to parse this by eye...

Configuring the Click History Buffer Size

 You can view the initial size of the circular buffer by issuing the following WLST command:

wlst> listLogHandlers(name="apps-clickhistory-handler")

This will return a chunk of information, including the all important buffer size. (some information omitted here for space reasons):

Handler Name: apps-clickhistory-handler
type:         oracle.core.ojdl.logging.QuickTraceHandlerFactory
useLoggingContext: true
encoding:	    UTF-8
bufferSize:	    880640
…
flushOnDump:	    false

To change the buffer size from the value of 880640bytes it's another WLST command:

wlst> configureLogHandler(name="apps-clickhistory-handler",
                          propertyName="bufferSize",
                          propertyValue="1048576")

So that's the basics of Click History - enjoy. 

Wednesday Sep 04, 2013

Basic WebLogic Deployment Plans for your ADF Application

In my recent article Dealing with ADF_FACES-30214 I alluded to the fact that you could automate the (re)setting of web.xml parameters using a deployment plan.  This is particularly useful to ensure that all of those development-time switches are toggled off when deploying the application into a UAT or production environment.  At this stage you could head off and just research deployment plans for WebLogic in the main WebLogic server documentation.. However, I wanted to show you how easy it is to create and use a deployment plan from within JDeveloper and to embed it into a deployment profile that can be used either from the IDE or via OJDeploy  in your build system.

To help illustrate this I\ve created a simple application which echos the values of the oracle.adf.view.rich.versionString.HIDDEN and javax.faces.PROJECT_STAGE context params onto the screen using EL. This is a 12c application, however, the same approach can be taken with JDeveloper 11g as well :

So the aim here is to update those values to their production-ready settings ("true" and "Production" respectively) using a deployment plan.  For convenience, I'm going to define it as part of the project contents, although strictly speaking where you put it is up to you, you'll just need to make sure that it's not packaged up inside of your deployment  artifacts (e.g. the WAR file) as it's only needed at deploy time.

Create the file from the New Gallery: Deployment Descriptors > WebLogic Deployment Descriptor and choose plan.xml in the Create WebLogic  Deployment Descriptor wizard - Step 1, as shown here:

On the next step of the wizard you can name the new file, I've called mine dpdProductionModePlan.xml. Step through the rest of the wizard accepting the defaults. Once the wizard has run you'll find the new file in your META-INF directory.  It's not too exciting:

<?xml version = '1.0' encoding = 'US-ASCII'?>
<deployment-plan xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://xmlns.oracle.com/weblogic/deployment-plan http://xmlns.oracle.com/weblogic/deployment-plan/1.0/deployment-plan.xsd"
                 xmlns="http://xmlns.oracle.com/weblogic/deployment-plan">
    <application-name>DeploymentPlan</application-name>
</deployment-plan>

The key task that we accomplish in a deployment plan, and the thing we actually need to do in this case, is value substitution and we can do that in a variety of deployment descriptors, including web.xml, the target here. 

Logically here's what happens.  The deployment plan defines one or more overrides for a particular descriptor (e.g. web.xml). It locates an element in the XML file using an XPath expression and replaces the target with the value of a named variable that is defined in the plan.

So let's start off with a variable definition, here's the variable that will be used to define the new value for PROJECT_STAGE

<variable-definition>
  <variable>
    <name>projectStageProduction</name>
    <value>Production</value>
  </variable>
</variable-definition>

So that's easy enough. Now we need to locate what descriptor file to override and the XPath expression that will identify the XML change to make.  Before starting this you will need to look at the deployment profile for your Web project (Project Properties > Deployment > Edit the profile you are using).  Grab the name of the WAR file as defined in the profile. Here's mine for my little demo app - the WAR file is called DeploymentPlanWebapp.war:

Once we have that information we can create the override section in the plan file where we locate the correct parameter in the web.xml, and substitute in the new value:

<module-override>
  <module-name>DeploymentPlanWebapp.war</module-name>
  <module-type>war</module-type>
  <module-descriptor external="false">
    <root-element>web-app</root-element>
    <uri>WEB-INF/web.xml</uri>
    <variable-assignment>
      <name>projectStageProduction</name>
      <xpath>/web-app/context-param/[param-name="javax.faces.PROJECT_STAGE"]/param-value</xpath>
      <operation>replace</operation>
    </variable-assignment>
  </module-descriptor>
</module-override>

You can see  the <variable-assignment> section and the XPath expression within that which is locating the correct context-param element from the collection of parameters based on the param-name value. The param-value is then replaced with the contents of the variable we defined before.

Once the  plan is complete we need to tell JDeveloper (or OJDeploy) to use it (Note that we can also use the same deployment plan when uploading an EAR file from the WebLogic console or using the web logic.Deployer task) .  

To set JDeveloper up, you just need to edit your Application (EAR) deployment profile (Application > Application Properties > Deployment > Edit Your Deployment Profile), then in the general section of the deployment profile property tree you can browse for and select the plan file you've just created:

Now it's simply a matter of deploying that application using the updated profile. Here I've deployed onto my standalone 12c WebLogic installation's managed server.  For completeness I've updated the plan to also replace the value for the oracle.adf.view.rich.versionString.HIDDEN value as well as the PROJECT_STAGE:

And here's the full plan file for reference:

<?xml version = '1.0' encoding = 'US-ASCII'?>
<deployment-plan
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://xmlns.oracle.com/weblogic/deployment-plan
         http://xmlns.oracle.com/weblogic/deployment-plan/1.0/deployment-plan.xsd"
   xmlns="http://xmlns.oracle.com/weblogic/deployment-plan">
  <application-name>DeploymentPlan</application-name>
  <variable-definition>
    <variable>
      <name>projectStageProduction</name>
      <value>Production</value>
    </variable>
    <variable>
      <name>devModeVersionDisplayOff</name>
      <value>true</value>
    </variable>        
  </variable-definition>
  <module-override>
    <module-name>DeploymentPlanWebapp.war</module-name>
    <module-type>war</module-type>
    <module-descriptor external="false">
      <root-element>web-app</root-element>
      <uri>WEB-INF/web.xml</uri>
      <variable-assignment>
        <name>projectStageProduction</name>
        <xpath>
          /web-app/context-param/[param-name="javax.faces.PROJECT_STAGE"]/param-value
        </xpath>
        <operation>replace</operation>
      </variable-assignment>
      <variable-assignment>
        <name>devModeVersionDisplayOff</name>
        <xpath>
          /web-app/context-param/[param-name="oracle.adf.view.rich.versionString.HIDDEN"]/param-value
        </xpath>
        <operation>replace</operation>
      </variable-assignment>
    </module-descriptor>
  </module-override>
</deployment-plan>

Of course there is more that you can do with deployment plans, now you have the basics have a read of the WebLogic documentation on the subject (12c link)

You can download the sample application that I used to illustrate this entry along with the deployment plan from the ADF EMG Samples Repository - download workspace.

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
« July 2015
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
31
  
       
Today