Friday, 29 February 2008

Deploying Oracle ADF: What did I learn about caching

Our company's production application server is located at a site different than the one we do our development. Whenever we deployed a new application users were always complaining about long response times, but we usually blamed that on the network. When we moved away from jsps and started working with JSF pages things really got out of hand.

Response times took as much as 10 seconds when application pages where accessed via WAN, rising the user complaint level to maximum levels.

Additionally the network link appeared to be utilized below 80% and then application server response times as shown at OC4J administration website where much below 0.045 sec.

First thing we tried was to use Oracle's ADF Cache library as explained in Chapter 23 of the ADF Programmers guide for Forms/4GL Developers, without any success. To be precise the library was indeed incorporated into the application but the server response time was still as long as 9 or 10 seconds.

It was then when we asked for help from Oracle Support. So thanks to Kavintha we learned the following :

  • First thing we learned was that the problem was indeed related to caching. Every JSF page created using Oracle ADF utilizes a Javascript file named Common10_1_3_3_0.js with a file size of 120 KB. This is an external script referenced at every page. The problem was that this script was never cached so users had to download it every time causing the delays mentioned above. If this file -- and all related page images -- are cached, then the usual size of an ADF/JSF generated HTML code ranges between 1 and 2 KBytes.
    Enabling full caching options on the Oracle Apache Server can be done by uncommenting the # CacheNegotiatedDocs line of the httpd.conf file located in $ORACLE_HOME/Apache/Apache/conf.
  • Next thing was that if an application is protected by Single-Sign-On then in that case, no matter whether CacheNegotiatedDocs is commented out or not, Apache will always send no-cache headers practically undoing whatever the Apache configuration or the ADF Faces Cache library do.
    The way to allow caching for SSO protected applications is to modify mod_osso.conf located in $ORACLE_HOME/Apache/Apache/conf so the entry for each application looks like this :
            <Location /ProductionDelays>
                    OssoSendCacheHeaders off 
                    require valid-user
                    AuthType Basic
            </Location>
    
    More information about how to to disable the caching headers sent by SSO is available on Metalink note 226119.1. Also you may have a look at a one of my previous postings regarding using SSO which is available through here
  • Last thing we learned about this caching business was that when a JSF application is accessed via SSL, then again caching issues still apply. Firefox will disable caching by default whenever the user loads an https:// URL. IE on the other hand does cache SSL pages by default but this setting can change by a policy set by your system administrator. I realize that in this case Firefox uses the safest approach but unfortunately this will produce very poor results if ADF/JSF pages are to be accessed.
    Enabling disk caching of SSL pages in Firefox can be done by changing the value of the browser.cache.disk_cache_ssl parameter available from the about:config URL.
    IE on the other hand has an option called Do not save encrypted pages to disk available in the Security section of the Advanced internet properties tab. If you want IE to cache content from SSL pages this option must be unchecked.

Wednesday, 27 February 2008

Changing the JDeveloper IDE font size

If you are like me and wish that the JDeveloper IDE displayed using a bigger font, then thanks to Khaled from Oracle support all you need to do is shut down JDeveloper, and then add a line like the following in the file ide.properties located in \jdev\system\oracle.jdeveloper.10.1.3.X.Y.

Ide.FontSize=14

Finally start JDeveloper and you are done.

Notes

  1. The actual X, Y values in the oracle.jdeveloper.10.1.3.X.Y may vary depending on your version of JDeveloper. In 10.1.3.3 X=41 and Y=57, while in 10.1.3.4 X=42 and Y = 70. Anyway the safest approach is to have a look at the Oracle IDE version in the versions tab of the JDev about box as show in the image below.
  2. Needless to say that the same goes for SQLDeveloper. The only difference is that he file to edit is located in ~/.sqldeveloper/system/oracle.sqldeveloper.1.2.1.3213. If using SQLDeveoper version 1.5 that comes with the new JDev11g look and feel then again the file chek is .sqldeveloper/system1.5.1.54.40/o.sqldeveloper.11.1.1.54.40/ide.properties.
  3. On Jdeveloper 11g the ide.properties file location is ~/.jdeveloper/system11.1.1.0.31.51.56/o.jdeveloper. Once more the actul path may be determined by looking at the Oracle IDE version from the about box.

Saturday, 23 February 2008

Oracle 11g How to determine the database character set

This is only a quick note for something I keep forgetting. So to determine the oracle database character set use the following simple SQL statement.

   select *
      from v$nls_parameters
      where parameter='NLS_CHARACTERSET'

Monday, 11 February 2008

Oracle 10g Killing open sesions

We have a client program that leaves lots of open sessions. These sessions take up server resources and we wanted to find a way to locate and kill them.

Thanks to Natasa Oikonomidou from Softworks who provided the code, I ended up with the following solution:

Create a the following procedure on the sys schema.

create or replace
PROCEDURE inactive_session_killer
IS
  now DATE;
BEGIN
  now := sysdate;
  FOR rec IN
    (SELECT sid,
       serial#,
       username,
       schemaname,
       osuser,
       terminal,
       machine,
       PROGRAM,
       status,
       type,
       saddr,
       logon_time,
       now - last_call_et / 86400 last_activity
     FROM v$session
     WHERE username = 'XXXXX'   -- user name of open session "leaver"
           AND type = 'USER')
  LOOP

    IF rec.last_activity <( now  - 8 / 24) THEN
      -- inactive for more than 8 hours
      -- you can keep a log table here: inserrt into ...
      EXECUTE IMMEDIATE 'alter system kill session ''' ||  
                                        rec.sid || ',' || 
                                        rec.serial#    || 
                                        ''' immediate';

    END IF;

  END LOOP;
END;

Using SQLPlus or SQL Developer execute the following

 
VARIABLE jobno number;
BEGIN
   DBMS_JOB.SUBMIT(:jobno, 'inactive_session_killer;', SYSDATE, 'SYSDATE + 1/24');    -- runs every hour
   commit;
END;

... and that does it.

Wednesday, 23 January 2008

Oracle IAS 10g 10.1.3.1 Installation on Enterprise Linux 5

Yesterday I completed my first installation of Oracle SOA Suite 10.1.3.1 on Oracle Enterprise Linux 5. In order to complete the installation I followed Metalink Note:465159.1, which explains the exact procedure.

Now the only tip I can give you, is to make sure that the /etc/hosts file contains a line that resolves the locally or DNS assigned computer name and domain to the machines network IP address.

My up until now experience has been with SuSE systems only, so on my machine the /etc/hosts/ file always contains a line like

10.5.1.8 lxbakalidis.shelman.int lxbakalidis

An OEL installation hosts file only contains

# Do not remove the following line, or various programs
# that require network functionality will fail.
127.0.0.1       localhost.localdomain   localhost       lxBakalidis
::1     localhost6.localdomain6 localhost6

... but this is not enough. The installation completed successfully only after I added the line that resolved the external IP to my computer name. So the final etc/hosts file looked something like this :

# Do not remove the following line, or various programs
# that require network functionality will fail.
127.0.0.1       localhost.localdomain   localhost       lxBakalidis
::1     localhost6.localdomain6 localhost6
10.5.1.8 lxbakalidis.shelman.int lxbakalidis

The symptoms before that were: First OUI was taking a very long time to do the file copy operation and next when at some point installation would complete, then two out of three configuration assistants would fail.

Friday, 11 January 2008

ABAP Get the value of a date type characteristic

When an object is classified with characteristics of type date then, using BAPI BAPI_OBJCL_GETCLASSES these characteristics are returned as numeric. That means that entries for date characteristics are found in the valuesnum array which --as you remeber from a couple of postings back -- is defined to be a standard table of bapi1003_alloc_values_num.

When I first saw this I thought that the actual date value could be acquired by assigning the value_from part of the returned bapi1003_alloc_values_num structure to a date variable, but it was not at all like this. The actual value is stored like an integer representing the ABAP internal date fromat which is YYYYMMDD. So an integer value like 20080112 represents January 12th 2008.

In ABAP code this translates as follows

  DATA :
    temp_int TYPE i,
    temp_date_str(8) TYPE c, 
    my_date TYPE d.

  FIELD-SYMBOLS :
     TYPE bapi1003_alloc_values_num.

      READ TABLE valuesnum
        WITH KEY charact = 'MY_DATE_CHARACT'
        ASSIGNING .

      IF sy-subrc = 0.
        temp_int = -value_from.
        temp_date_str = temp_int.
        my_date = temp_date_str.
      ENDIF.

Java: Dates/Times and Calendars

The problem that many new Java programmers -- me included -- face when they start dealing with dates and times is : Given a java.util.Date object get month, date and year fields in separate variables.

The usual misunderstanding here is that the class java.util.Date is almost deprecated and all things you expect to be able to perform with dates are done using the java.util.Calendar; class.

Calendar is an abstract class whose javadoc can be found here. The class provides get and set methods that allow you to alter any of the fields that constitute a point in time. The following example demonstrates one way to calculate the date corresponding to the start of the current term, using the Calendar get and set functions. It also shows how to convert a Date to a Calendar and vice versa.

    private void calStartOfCurrentTerm()
    {
        // create a new Gregorian Calendar
        Calendar c = new GregorianCalendar();
        // you can synchronize a Calendar with a date by using the
        // setTime() method
        c.setTime( new Date());
        
        // once you get a Calendar extracting field information is 
        // as easy as ....
        int month = c.get( Calendar.MONTH);
        int day = c.get(Calendar.DAY_OF_MONTH);
        int year = c.get(Calendar.YEAR);
        
        // let's calculate the date that corresponds to the 
        // start of the current term
        int newMonth;
        
        switch (month) {
        case Calendar.JANUARY:
        case Calendar.FEBRUARY:
        case Calendar.MARCH:
            newMonth = Calendar.JANUARY;
            break;
        case Calendar.APRIL:
        case Calendar.MAY:
        case Calendar.JUNE:
            newMonth = Calendar.JANUARY;
            break;
        case Calendar.JULY:
        case Calendar.AUGUST:
        case Calendar.SEPTEMBER:
            newMonth = Calendar.JULY;
            break;
        default:
            newMonth = Calendar.OCTOBER;
        }
        
        // adjust the calendar to point to the new date
        c.set(Calendar.MONTH, newMonth);
        c.set(Calendar.DAY_OF_MONTH, 1);
        
        // get the information to a new date variable
        Date startOfTerm = c.getTime();
        
        // display results
        System.out.println("Day is " + day +" month is " + month + " year is " + year);
        System.out.println( startOfTerm.toString());
    }

Here is a slightly different version that returns an object of type oracle.jbo.domain.Date

import java.sql.Timestamp;

import java.util.Calendar;
import java.util.GregorianCalendar;

import oracle.jbo.domain.Date;

  private Date getStartOfTerm()
  {
    // create a new Gregorian Calendar
    Calendar c = new GregorianCalendar();

    // once you get a Calendar extracting field information is 
    // as easy as ....
    int month = c.get(Calendar.MONTH);

    // let's calculate the date that corresponds to the 
    // start of the current term
    int newMonth;

    switch (month) {
      case Calendar.JANUARY:
      case Calendar.FEBRUARY:
      case Calendar.MARCH:
        newMonth = Calendar.JANUARY;
        break;
      case Calendar.APRIL:
      case Calendar.MAY:
      case Calendar.JUNE:
        newMonth = Calendar.JANUARY;
        break;
      case Calendar.JULY:
      case Calendar.AUGUST:
      case Calendar.SEPTEMBER:
        newMonth = Calendar.JULY;
        break;
      default:
        newMonth = Calendar.OCTOBER;
    }

    // adjust the calendar to point to the new date
    c.set(Calendar.MONTH, newMonth);
    c.set(Calendar.DAY_OF_MONTH, 1);
    c.set(Calendar.HOUR_OF_DAY, 0);
    c.set(Calendar.MINUTE, 0);
    c.set(Calendar.SECOND, 0);
    c.set(Calendar.MILLISECOND, 0);
    
    // get the information to a new date variable
    java.util.Date javaDate = c.getTime();
    Timestamp st = new Timestamp(javaDate.getTime());
    return new Date(st);
  }

PS:. Finally There is one last thing I have to get used to. "Copy as HTML" on JDeveloper 10g, does not work on openSUSE 10.2.

Thursday, 10 January 2008

ABAP Obtaining characteristics of a document from the classification system

In addition to a previous post regarding getting classification information for objects of type Material or Batch and thanks to additional info and feedback that I received from our MM specialist Ms Marilena Svolaki, I will be able to provide an example of how to do the same thing with documents.

Master information about documents is found in the DRAW table. (This is short for German Dokumentinformationssatz). The key fields for each document are displayed in the following image :

Creating the object key for BAPI_OBJCL_GETCLASSES is performed by concatenating the document type (field DRAW-DOKAR), the document number (field DRAW-DOKNR), the document version (field DRAW-DOKVR) and the document part (field DRAW-DOKTL). In code this can be expressed as follows

 DATA
    wa_draw TYPE draw, 
    cur_bapi_objectkey LIKE inob-objek,

    class_list TYPE STANDARD TABLE OF bapi1003_alloc_list,
    valueschar TYPE STANDARD TABLE OF bapi1003_alloc_values_char,
    valuescurr TYPE STANDARD TABLE OF bapi1003_alloc_values_curr,
    valuesnum  TYPE STANDARD TABLE OF bapi1003_alloc_values_num,
    return     TYPE STANDARD TABLE OF bapiret2.

*   Make wa_draw somehow correspond to a valid document entry ....     
    CONCATENATE wa_draw-dokar
                wa_draw-doknr
                wa_draw-dokvr
                wa_draw-doktl
                INTO cur_bapi_objectkey.

*   Call BAPI to get the list of characteristics
    CALL FUNCTION 'BAPI_OBJCL_GETCLASSES'
    EXPORTING
      objectkey_imp         = cur_bapi_objectkey
      objecttable_imp       = 'DRAW'
      classtype_imp         = '017'
      read_valuations       = 'X'
      keydate               = sy-datum
*     language              = 'E'
    TABLES
      alloclist             = class_list
      allocvalueschar       = valueschar
      allocvaluescurr       = valuescurr
      allocvaluesnum        = valuesnum
      return                = return.

    READ TABLE return INDEX 1 ASSIGNING <ret>.

*   Check that object allocations exist
    IF  sy-subrc = 0 AND
        <ret>-id = 'CL' AND
        <ret>-number = 741.

*      Read characteristic values
       ...
    ENDIF

Wednesday, 12 December 2007

Oracle 10g SQL Mass rebuild and move the indexes of a specified user

Here is a function that rebuilds or coalesces the indexes of a specified user.If asked to rebuild the indexes then you can supply a table space to move the rebuild indexes into.

The function takes three parameters :

  • user_name : The name of the user whose indexes are to be updated
  • rebuild_type : Ether C or R to indicate Coalesce or Rebuild operation
  • tablespace_name : Optional parameter to indicate where to move the indexes in case of rebuild

The function should be executed by a user with DBA privileges and this code is as follows

create or replace PROCEDURE rebuild_user_indexes( user_name IN VARCHAR2, 
                                rebuild_type IN VARCHAR2, 
                                tablespace_name IN VARCHAR2 DEFAULT NULL) 
AS
  full_index_name VARCHAR2(100);
  index_command VARCHAR2(10);
  sql_str VARCHAR2(100);
  
  total_indexes Integer;
  updated_indexes Integer;
  
  CURSOR get_index_cursor IS
    SELECT index_name, index_type 
    FROM dba_indexes
    WHERE owner = user_name AND
          index_type in ( 
              'BITMAP', 
              'NORMAL', 
              'FUNCTION-BASED NORMAL', 
              'FUNCTION-BASED DOMAIN');
      
BEGIN
  /* check initial parameters */
  IF rebuild_type <> 'R' AND rebuild_type <> 'C' THEN
    raise_application_error( -20999, 'rebuild type should be "C" or "R"');
  ELSIF rebuild_type = 'R' THEN
    index_command := ' Rebuild';
    /* if a rebuild tablespace is also defined */
    IF tablespace_name <> '' THEN
      index_command := index_command || ' tablespace ' || tablespace_name;
    END IF;
  ELSE
    index_command := ' Coalesce';
  END IF;
  
  total_indexes := 0;
  updated_indexes := 0;
  /* get all indexes that belong to the specified user */
  FOR r IN get_index_cursor LOOP
    total_indexes := total_indexes + 1; 
    dbms_output.put_line( total_indexes || ' ' || 
                          r.index_name || ' ' || 
                          r.index_type);

    /* Coalescing works only on normal B-Tree indexes, 
     * while rebuilding works with everything.
     */    
    IF (rebuild_type = 'R') OR 
       (rebuild_type = 'C' AND instr( r.index_type, 'NORMAL') > 0) THEN
      
      full_index_name := user_name || '.' || r.index_name;
      sql_str := 'alter index ' || full_index_name || index_command;
      
      BEGIN
        /* attempt to modify the index */
        EXECUTE IMMEDIATE sql_str;
        updated_indexes := updated_indexes + 1;
      EXCEPTION
        WHEN OTHERS THEN
          /* display the command and the error that occured during index 
           * update 
           */
          dbms_output.put_line( sql_str);
          dbms_output.put_line( sqlcode || ' ' || sqlerrm);
          /* continue working */
          NULL;
      END;
    END IF;
  END LOOP;
    
  /* report results */
  dbms_output.put_line( 'Indexes examined :' || total_indexes);
  dbms_output.put_line( 'Indexes Updated  :' || updated_indexes);
END rebuild_user_indexes; 

Thanks to SQLDeveloper running the function can is as easy as copying and pasting the following in SQLPlus.

DECLARE
  USER_NAME VARCHAR2(200);
  REBUILD_TYPE VARCHAR2(200);
  TABLESPACE_NAME VARCHAR2(200);
BEGIN
  USER_NAME := NULL;       -- add your user here, for example 'HR'
  REBUILD_TYPE := NULL;    -- either 'C' or 'R'
  TABLESPACE_NAME := NULL; -- optional new tablespace name if desired

  REBUILD_USER_INDEXES(
    USER_NAME => USER_NAME,
    REBUILD_TYPE => REBUILD_TYPE,
    TABLESPACE_NAME => TABLESPACE_NAME
  );
END;

Note

The above code provides a very simplistic approach to index rebuilding as it rebuilds all indexes of a user. In order to determine the indexes in your database that would really benefit from rebuilding you need to run the script available from Oracle Metalink Note:122008.1.

Tuesday, 11 December 2007

JDeveloper OC4J Changing the language that eror messages are displayed

I have had this problem many times. During development when something went wrong with my application, all I could get after the Internal server error would be JBO:XXXX error code with numerous question marks instead of the actual error message.

Some people advised me to change the preferred language/locale of my WEB browser and make it English, so as to force the display of error messages in this language, but that didn't work either.

Today I found a remedy. Open the project properties Go to Run/Debug click edit and add -Duser.language=en -Duser.country=US in the java command line options, like I show you in the following pciture

... and that does it.

Thursday, 6 December 2007

ADF BC Canceling edits in the current record of a ViewObject

The number of things that one man misses becomes frightening especially when something is as crucial as a cancel edits button in a record display form.

Up until now I have been using a Rollback action binding to cancel any edits. Of course that was also casing current record information to be lost, so I had to rely on Steve Muench's Restore Current Row After Rollback from his Not Yet Documented ADF Sample Applications in order to keep my data in sync..

Revelation came as I as was browsing for the 9999th time the 10.1.3.1 version of the Tutorial for Forms/4GL Developers. The magic function that makes a view record refresh and forget any changes is as simple as that.

public void cancelEditsToCurrentServiceRequest() {
    getServiceRequestMain().getCurrentRow().refresh( 
                            Row.REFRESH_WITH_DB_FORGET_CHANGES);
}

Shame on me!

On second thought Muench's approach is more centric and may come in handy especially if a program has many view objects through which users enter data. Of course writing cancelEdits functions is clearer but has to be done for every view object that we require to use. The choice is still ours.

Thursday, 29 November 2007

ADF-BC/JSF Performing case insensitive searches

I have been asked to implement a service request system that will allow a company to record service requests from customer via a telephone line. The difference with oracle's SRDemo application is that in my case SR's will be entered by telephone operators and be assigned to departments and not to technicians

Anyway the application still offers a SRSearch page that allows telephone operators and managers to filter the service request list and here is where my problem started since they asked me if the QBE system would match parts of the requester first or last name case insensitively.My first reaction was to tell them to enter everything in capital letters, but then I though I might give it a try and ended up like this

first I added two additional fields in my query named SearchFirstName and SearchLastName defined like this

  SELECT 
       .....
       ServiceRequest.REQUESTER_FIRST_NAME, 
       UPPER( REQUESTER_FIRST_NAME) AS SEARCH_FIRST_NAME, 
       ServiceRequest.REQUESTER_LAST_NAME, 
       UPPER( REQUESTER_LAST_NAME) AS SEARCH_LAST_NAME, 
       ......

then changed I used JavaScript in the SRSearch page to make sure that whatever the user enters in the SearchFierstName and SearchLastName fields is converted to upper case before posting.

The javascript file itself is as simple as this

/* ---------------------------------------------------------------------------
 * file SRSearchSubmit.js
 * contains code that allows to submit the SRSearch form with the first name
 * and last name fields converted to upercase.
 *
 * Date 03-12-2007
 * ---------------------------------------------------------------------------
 */
 function convert()
 {
    var firstName = document.forms["myADFForm"].elements["myADFForm:searchFirstNameField"].value;
    var lastName =  document.forms["myADFForm"].elements["myADFForm:searchLastNameField"].value;

    document.forms["myADFForm"].elements["myADFForm:searchFirstNameField"].value
      = firstName.toUpperCase();
    document.forms["myADFForm"].elements["myADFForm:searchLastNameField"].value
      = lastName.toUpperCase();

    return true;
 }

No the two final points. First we need to include the javascript file at the top of the page inside a <f:verbatim> tag. Next in order to get the names of the elements right, we need to set the id attribute of both the <h:form> and the <af:inputText> elements.

The page header part of the resulting JSF page looks like this

    <afh:html>
      <f:loadBundle basename="SRKomo" var="res"/>
      <afh:head title="#{res['application.title']}">        
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <link href="../css/oracle.css" rel="stylesheet" media="screen"/>
        <f:verbatim>
          <script src="../javascript/SRSearchSumbit.js" type="text/javascript"></script>
        </f:verbatim>
      </afh:head>
      <afh:body>
        <af:messages/>
        <h:form id="myADFForm" onsubmit="convert();">
        ...

And the definitions for the each of th two search fields like this

              <af:panelLabelAndMessage label="#{bindings.AllRequestsListSearchFirstName.label}">
                <af:inputText value="#{bindings.AllRequestsListSearchFirstName.inputValue}"
                              simple="true"
                              required="#{bindings.AllRequestsListSearchFirstName.mandatory}"
                              columns="#{bindings.AllRequestsListSearchFirstName.displayWidth}"
                              styleClass="searchTextField"
                              id="searchFirstNameField">
                  <af:validator binding="#{bindings.AllRequestsListSearchFirstName.validator}"/>
                </af:inputText>
              </af:panelLabelAndMessage>

and that did it.

Notes

  • The original info about how to place javascript into a JSF document comes from the Frank Nimphius' Blogbuster article ADF Faces: Submit a form with the Enter key.
  • I have found it very easy to understand the HTML produced by the Oracle JSF engine using Firebug. Especially the "Inspect element" function will locate the HTML code of each eleent you click on and that can save you an awfull lot of decoding trouble.

Wednesday, 28 November 2007

Where is the Oracle Database alert log located ?

This is something I keep forgetting so I think that I may as well add it here.

On my Linux system the alert log is located in $ORACLE_HOME/admin/$ORACLE_SID/bdump while the exact same path -- with "\" instead of "/" -- is used for Windows.

The directory where the alert.log found is determined by the background_dump_dest initialization parameter: So the ultimate way to find it is by issuing an SQL command like :

select value 
    from v$parameter 
    where name = 'background_dump_dest';

Needless to say that the alert.log's name is alert.log and that it is a simple text file that can be opened using any text editor and that you can delete it or shrink it online.

Friday, 9 November 2007

ADF/JSF Getting the current row with code executed from a table bucking bean

The following code comes from the JDeveloper Forums courtesy of Frank Nimphus. If added to any event listener fired from "inside" an ADF Table, it prints the row's 1'st attribute and the rowKey.

        FacesContext fctx = FacesContext.getCurrentInstance();
        ValueBinding vb = (ValueBinding) fctx.getApplication().createValueBinding("#{row}");
        JUCtrlValueBindingRef rwbinding = (JUCtrlValueBindingRef) vb.getValue(fctx);
        System.out.println("--- Key as String --"+rwbinding.getRow().getKey().toStringFormat(true));
        System.out.println("--- First Attribute ---"+rwbinding.getRow().getAttribute(1));

Thanks again Frank

ADF/JSF Avoiding uncought exeptions after commit.

When using Oracle JDeveloper 10.1.3.x, you can find it very tempting to create "Create/Edit Record" pages by dropping an ADF Form on a blank JSF page and then adding commit and rollback buttons in the place of the ok and cancel buttons with an action property that returns your users to the browse page.

So let's assume that your faces-config file contains a navigation case labeled "return", that leads from you edit page back to your browse page. In cases like these, the code for the commit button will more or less look like this

                      <af:commandButton 
                                     actionListener="#{bindings.Commit.execute}"
                                     text="Ok" 
                                     action="return"
                                     id="commitButton"
                                     disabled="false"/>

This approach, presents a very serious hazard, which is a bug in Oracle's JSF implementation. If any exception is thrown after commit, the framework moves to the page designated by the navigation case and the error messages are not being displayed at all.

To make matters worse, you end up with on open transaction and nothing else entered in this manner ever gets stored in your database while you users have no way to realize that something has gone wrong. I had raised an SR for this that ended up in a one off patch (5630740) for JDeveloper 10.1.3.2.

I run into this problem again in am application that I developed recently using JDeveloper 10.1.3.3. And then it hit me. The solution was as simple as the Egg of Columbus. The only thing that needs to be done is to bind the commit button action in a backing bean. JDeveloper will then change the button tag into something like :

                      <af:commandButton 
                                    text="Accept"
                                    id="commitButton"
                                    disabled="false"
                                    action="#{backing_Edit.commitButton_action}"/>

... create the following code :

    public String commitButton_action()
    {
        BindingContainer bindings = getBindings();
        OperationBinding operationBinding =
            bindings.getOperationBinding("Commit");
        Object result = operationBinding.execute();
        if (!operationBinding.getErrors().isEmpty()) {
            return null;
        }
        return "return";
    }

... and that does it. Commit exception messages are displayed correctly and everyone is happy.

In case you have many edit pages with different navigation case labels then you might wish to create a solution that is a little more sophisticated and closer to the OO paradigm. In a case like this I suggest that you create a base class to be the parent of all your page backing beans and add the following method.

public class MyBackingBase
{
    protected String executeCommit( BindingContainer bindings, String successValue)
    {
        OperationBinding commitBinding =
            bindings.getOperationBinding("Commit");
        
        commitBinding.execute();
                
        return commitBinding.getErrors().isEmpty() ? successValue : null;
    }
 }

Having done that the actual backing bean code could be something as simple as

    public String commitButton_action()
    {
        return executeCommit( getBindings(), "return");
    }

... and one more thing. The Copy as HTML function is not available again. I guess I 'll have to start looking why .. :-(

Wednesday, 7 November 2007

JSF Passing parameters to included jsp pages

The question was brought up in the JDeveloper forum. When I started creating my first JSF pages I wanted to be able to create a menu that I could edit and make make the changes propagate to all pages immediately. So What I wanted back then was amethod to include a jsp to all my pages but also be able to pass parameters from the including page to the one being included.

The answer came from the book Mastering JavaServer Faces by Bill Dudney, Jonathan Lehr, Bill Willis and LeRoy Mattingly.

The approach is to create a subview and then include the page using a jsp:include tag. Inside the include you must use a jsp:param tag where you define the name and value of the expected JSP parameter. To cut the long story short, I tested the solution with JDeveloper 10.1.3.3 and here is the code fragment to use.

            <f:subview id="header">
                <jsp:include page="/header.jsp" flush="true">
                    <jsp:param name="pageTitle" value="Welcome to my first JSF Application"/>
                </jsp:include>
            </f:subview>
 

The included page should be surrounded by an f:subview tag. The external parameter can be accessed by an EL expression like "#{param.paramName}" and a simple example would be something like :

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@ page contentType="text/html;charset=ISO-8859-7"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://xmlns.oracle.com/adf/faces" prefix="af"%>
<%@ taglib uri="http://xmlns.oracle.com/adf/faces/html" prefix="afh"%>
<f:subview id="header">
  <afh:html>
    <afh:head>
      <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-7"/>
    </afh:head>
    <afh:body>
      <af:panelHeader text="#{param.pageTitle}"/>      
    </afh:body>
  </afh:html>
</f:subview>

... and one last comment. I guess that this is the first time that the Copy as HTML command works correctly in JDeveloper on Linux. :-)

Tuesday, 6 November 2007

BAPI / RFC with Delphi 2006 / 2007

Six months ago I wrote an article regarding how to install and use SAP's Active X components using Delphi 6. Here is the sequel regarding Delphi 2006 and 2007.

The basic idea is still the same you only need to install SAP Logon Control version 1.1, SAP BABPI Control Version 1.2 and finally import the SAP Remote Function Call Controll (Version 5.0) type library.

To complete the task in Delphi 2006, use the following procedure:

  1. Start Delphi 2006 and create a new package using the menu File → New → Package for Delphi Win 32.
  2. Save the package and the project using a name like SAPControls
  3. From the Delphi main menu select Component → Import Component.
  4. Locate the SAP Logon Control as show in the following image and click next
  5. In the following Screen just type SAP at the pallete page name ... and press next
  6. On the next screen select Add unit to Package radio item. ... and click Finish
  7. Repeat the previous steps and install the SAP BAPI Control and the Remote function type library. Unlike Delphi 6 you should get no compiler error messages.
  8. When you are finished, right click on the SAPControls package on the project manager window and select Install from the pop up menu.
  9. That does it. Create a new project and use the components of the new SAP pallete Category as you please.

Don't worry if you misplaced your components in an other category. You can drug them into any category you wish afterwards.

Friday, 19 October 2007

JDeveloper 10.1.3.3 JSP Compiler Errors in JSF faces

I have run into the following error while modifying JSF pages in JDeveloper 10.1.3.3 on openSUSE. While modifying the page contents using the JDeveloper editor, the page stops working when accessed from the browser and you get an Internal Server Error with a JspCompileException but nothing else. The page was still accessible from the internal JDeveloper Design editor and would complie correctly from inside JDeveloper.

A remedy that sometimes worked was to remove a few components from the page put them back and then hope it would work.

Eventually I came up with the following simple trick. Just remove all contents from ~/jdevhome/mywork/WorkSpace/ViewController/classes/.jsps and rebuild your project.

Thursday, 18 October 2007

Delphi: How to install BDE on a client machine

Suppose you have a Delphi application that access data using the Borland Database Engine (BDE) and you wish to deploy it on a user's client computer. If you compile the project without runtime packages and no BDE support is required, then merely copying the EXE file on the target computer is usually enough.

Now installing BDE on a client computer is performed as follows :

  1. Locate the file bdeinst.cab located in C:\Program Files\Common Files\Borland Shared\BDE.
  2. Open it using winZip or something equivalant.
  3. The .cab file contains only one file BDEINST.DLL. Extract this to the users client computer.
  4. Use the command regsvr32.exe bdeinst.dll from the command prompt to perform the installation.
  5. Run your application

BDE un-installation can be performed by folloowing these steps :

  1. Delete the folder containing BDE data
  2. Delete the BDEADMIN.CPL file in the Wiindows\System folder, in order to get rid of the control file applet
  3. Use regedit in order to delete the key HKEY_CURRENT_USER\Software\Borland\BDEAdmin
  4. Next locate HKEY_LOCAL_MACHINE\Software\Borland and delete the subkeys BLW32, Borland Shared and Database engine.
  5. Reboot your machine

Note : Having said all that I would like to give you an extract from the file bdedeploy.txt located at the BDE installation directory.

2. BORLAND-CERTIFIED INSTALLATION PROGRAMS
===========================================================
Borland products which include redistribution rights
include an Borland-certified install program, such as
InstallShield Express, to ensure proper installation and
uninstallation of your applications so that they co-exist
well with other applications which use the Borland Database
Engine (BDE) including those created with Visual dBASE,
Paradox, Delphi, and C++Builder.

Borland has also provided BDE and SQL Links installation
information to other install vendors such as Sax Software,
WISE Software, Great Lakes Business Systems (GLBS) makers
of the WISE install tool and Eschalon Development so that
their products can also be ensured to be fully compatible
with the BDE.

From time to time, Borland Software Corporation may, 
at its discretion, certify additional installation programs 
for use as the Borland Certified Install Program for this 
product.
  
Also check the Borland-sponsored announcement newsgroups:

  news:borland.public.announce
  news:borland.public.bde

3. BDE DEPLOYMENT (ALL DATABASE APPLICATIONS)
===========================================================
3.1 Deploying the BDE
---------------------
A Borland-certified installation program provides all
needed functionality and steps for redistributing the
Borland Database Engine (BDE), including:

  * Selecting files to redistribute
  * Determining final directory locations
  * Comparing versions of BDE files
  * Creation of BDE aliases
  
Follow the instructions of the particular installation
program used for specific steps to create an installation
program that includes the BDE.

Wednesday, 17 October 2007

JSF How to create and use dependent list boxes

Back in 2006, Frank Nimphius has written two excellent articles about using AJAX style combo (or list) boxes in a web page that filter one another based on values of a master detail relationship. I have to admit that thanks to him, my users have one less reason to grumble :-)

The first one is entitled ADF Faces: How-to build dependent lists boxes with ADF and ADF Faces and the second one ADF Faces: How-to build dependent lists boxes with ADF and ADF Faces Part - II

Thank you Frank.