Uploading Excel Spreadsheets Into Ebusiness Suite: Oracle
Uploading Excel Spreadsheets Into Ebusiness Suite: Oracle
Uploading Excel Spreadsheets Into Ebusiness Suite: Oracle
premiertec
Miroslav Samoilenko
October 2009
Overview
Almost any Oracle eBusiness Suite implementation faces a requirement to upload data from Excel.
Why? Sometimes, because the end users are used to Excel interface. For example, purchasing
department has its own Excel template for requisitions whichs been used for years and everybody
likes it and wants to keep on using it. There are also situations where Excel provides a perfect
environment for data manipulation and preparation. We found this to be the case almost on all our
implementations of Oracle Project Accounting with monthly updates to project budgets.
Upload of complex data structures from Excel, such as sales orders or purchase requisitions, requires
significant Visual Basic programming. Such Excel workbooks require strict layout structure, built in
real-time validations, production database connection, and are specific to the customer.
Construction cost of such a template should be measured against the productivity gains. Since
an Oracle eBusiness Suite implementation team typically lacks knowledge of Visual Basic, the cost
of such an extension is high. We have built several Excel templates for sales order upload and
manipulation using Excel 2007 for Oracle eBusiness Suite 11.5.10.
In this article I would like to discuss an approach to load Excel spreadsheets with tabular data, such
as project budgets or meter reads, into Oracle eBusiness Suite.
The simplest way to load data from Excel into an Oracle database is:
This approach shows the major data transformation needs, i.e. data should:
Technical Design
We build a new OAF page for the user to specify the file to upload. Since we are building a generic
mechanism, the user also specifies the purpose of the upload. For example, price lists, or project
budgets, or manual invoices. The list of purposes is presented as a table with name and description
of the purpose for the upload. An advanced implementation can also display a URL for the file
template which users can download and populate with data.
The purposes can be defined via a lookup and extracted by a query below:
select lookup_code
, meaning
ID NUMBER PrimaryKey,generated
, description
, attribute1 template_url
from fnd_lookup_values_vl
where lookup_type = XXPT_FILE_UPLOAD_PURPOSE
and enabled_flag = Y
and sysdate between start_date_active and nvl(end_date_active, sysdate)
and view_application_id = 3
and security_group_id = 0
Since we are building an OAF page, we have two places for data processing. We can parse the file
inside the page controller, and place the data into the destination tables directly. This
approach allows for immediate scan of the incoming data and error reporting. The drawback is
that any change to the list of files or to the file structure itself requires changes to Java code.
The only other place where we can place the data is the database. The incoming file can be
placed into a customer table into a CLOB field for further processing. For that purpose, we create a
table XXPT_FILE_UPLOAD_TMP with the following fields
Since the user can upload binary files such as Excel workbooks, the page controller needs to rec-
ognize those and transform into an ASCII file. In out example we will use JExcelAPI library (http://
jexcelapi.sourceforge.net/) to transform Excel sheets into CSV stream.
We anticipate that most of uploads are for data stored in a tabular format. This assumption makes
SQL*Loader our preferred data upload tool. The tool is very generic and can upload any delimited
or fixed width flat file into the destination tables. Changes to the data structure can easily be ad-
dressed by changing the control file. This change can be carried by most technical consultants even
without knowledge of Oracle eBusiness Suite.
However, it makes storing data in a database CLOB very impractical. We need it in a flat file
somewhere where SQL*Loader can read. Since SQL*Loader is submitted as a concurrent request,
we need to store the flat file in a place from which the concurrent manager can read it, for example,
an output of another concurrent request.
To achieve this, we will build a Java concurrent program that is submitted immediately after the file
is uploaded into XXPT_FILE_UPLOAD_TMP table. The concurrent program dumps file contents into its
output stream. This concurrent program achieves out file transport goal, i.e. data file is transported
to a place where it can be processed by Oracle eBusiness Suite.
The output of the concurrent program can now be passed to the SQL*Loader as the input data
file. If the data file is an XML message, we can pass it to BI Publisher for processing or write our own
Java concurrent program to parse and process the message. We will use the implementation of the
Chain of Command described in previous articles to chain the processing concurrent program.
Building UI
First of all, we need to define business objects and explain their usage. The page we are building
contains one field on top of the page where the user specifies the file to upload, and then a table
below where user select the purpose for the upload.
The content for the table comes from a view object FileUploadPurposeVO which is sourced by the
query from the technical design. We add to it a transient updatable attribute Selected to allow for
single table selection.
The content of the uploaded file is stored in the database table XXPT_FILE_UPLOAD_TMP. So, we
create an entity object XXPTFileUploadTmpEO and corresponding view object XXPTFileUploadTmpVO.
We also need a primary key generator. The first thought, of course, is to define a custom sequence.
I do not like this idea since the primary key here does not need to be sequential; it just needs to be
unique. So, the sequence will be an extra custom database object without a very good need. We
can also define or reuse a document sequence. However, this is a lot of application setups for
a primary key which will live only a couple of seconds.
The approach I prefer is to use the GUID generated by the database and convert it into a number.
The following query generates unique number each run you execute it.
We build a view object PrimaryKeyGeneratorVO based on this query. This view object completes our
data model.
There are many examples available over the internet and in the Developers Guide on how to build
an OAF page. We will skip those here. The relevant assumption is that the table which displays the
upload purposes is a single selection table with an action button submitButton.
This makes the page controller entry point look like this:
/**
* Procedure to handle form submissions for form elements in
* a region.
* @param pageContext the current OA page context
* @param webBean the web bean corresponding to the region
*/
public void processFormRequest(OAPageContext pageContext, OAWebBean webBean)
{
super.processFormRequest(pageContext, webBean);
OAApplicationModule am = pageContext.getApplicationModule(webBean);
if (pageContext.getParameter(submitButton) != null)
{ processFileUpload(pageContext, webBean, am);
}
}
Method processFileUpload is performing the following tasks:
There are several open source and commercial Java libraries that are capable of reading Excel work-
books. Among those are Apache POI (http://poi.apache.org/), JExcelAPI (http://jexcelapi.source-
forge.net/), JCom (http://sourceforge.net/projects/jcom/), ExtenXLS7 (http://www.extentech.com/
estore/product_detail.jsp?product_group_id=1) to name a few. We will use JExcelAPI to recognize
and process Excel files.
We assume that users are loading either Excel workbooks or ASCII files, such as XML documents or
CSV files. This makes streamToString method looks like this.
This method interprets the mimeType of the inbound file, and either reads it as an Excel workbook using
method xlsToString(InputStream) or converts it into a string using method streamToString(InputStream).
We will omit details of streamToStream(InputStream) method, as it reads characters from the input
stream and appends them to a string, and concentrate on xlsToString(InputStream).
The body for this method was borrowed from here (http://www.java-tips.org/other-api-tips/jexcel/
converting-excel-documents-to-csv-files.html). The method interprets the input stream as an Excel
workbook and converts each sheet into a comma separated format.
return stringWriter.toString();
}
Method formatExcelCell is responsible for converting the contents of a cell into the one compat-
ible with CSV file format. Namely:
Here we reached the point when the incoming stream is tested to be an Excel workbook, and if so,
converted into a comma separate stream. Otherwise, it is assumed that the incoming file is an ASCII
stream.
Once we have the input stream converted into an ASCII string, we need to store it in the custom
table. To perform this operation, we need to generate primary key, and extract the purpose for the
upload.
We already discussed the query which generated globally unique primary key, and creation of a
view object PrimaryKeyGeneratorVO based on this query. The method which generates the primary
key becomes:
In order to determine the select purpose, we need to walk through each row of purposes in the
current range, and locate the one with checked attribute Selected, since this was the transient
attribute designated to work with table single selection. The method is:
Now, we are ready to store data in the database. We will need the purpose of the upload later
when we submit the concurrent program to print the file contents in its output. So, we extract the
purpose separately and pass as a parameter to the storage procedure, which now looks like:
The method returns the primary key of the newly created record. We will later pass this key to the
concurrent program that prints the output of the CLOB into its output.
Though this template is small, it does illustrate the key differences between Java and PL/SQL
concurrent program.
By default, Java concurrent program completes with an error, unlike PL/SQL program which
completes successfully. Hence, it is imperative to set up not only completion in error, but also when
program completes successfully.
At the end of the execution of the Java concurrent program, you must release JDBC connection
back to the pool.
Reading Parameters
Concurrent program parameters are accessible via method cpcontext. getParameterList. This
method returns an instance of oracle.apps.fnd.util.ParameterList class. This class provides an
Enumeration interface to access all the parameters passed to the program in the order defined
during the concurrent program registration. This class has one interesting drawback. You can read
the parameters only once.
I found it useful to have a utility that converts the ParameterList into a Map.
if (nameValueType.getValue() != null)
{
result.put(nameValueType.getName(), nameValueType.getValue());
}
}
return result;
}
Map provides a better controlled access to the list of parameters. The method can be enhanced to
recognize the type of the value and convert them from String into BigDecimal or Date.
Reporting Exception
If you really want the user to see the exception stack, you need to print it into the oracle.apps.fnd.
cp.request.LogFile. An instance of this class is available via cpcontext.getLogFile(). This class pro-
vides an OutputStream like interface, though it is not an implementation of java.io.OutputStream.
LogFile provides a set of methods to write strings into the output stream
write(String message, int level)
Here, level specifies the debugging level which can be set from LogFile.STATEMENT to LogFile.EXCEPTION.
In order to report exception to the log file, you can use the following code
StringWriter writer = new StringWriter();
ex.printStackTrace(new PrintWriter(writer));
logFile.writeln(writer.toString(), logFile.EXCEPTION);
Writing Output
The key task of the concurrent program which we are writing is to read the CLOB that contains the
file contents and write it into its output stream. The output of a concurrent program can later be
accessed by SQL*Loader, other concurrent program or, even, OAF web pages and services outside
of Oracle eBusiness Suite.
To access the database, we need to get hold of the database connection. An instance of the
connection can received from cpcontext.getJDBCConnection().
The following method retrieves the CLOB from the database as and InputStream which is then
converted into a String:
cpcontext.getOutFile().write(fileContents);
Oracle provides Java classes to submit a concurrent program. Definitely, one can call PL/SQL pro-
cedure directly using JDBC connection, though the Java wrapper is much nicer and easier to use.
Class oracle.apps.fnd.cp.request.ConcurrentRequest represents submission of a concurrent request.
You can setup optional layouts, notifications, schedule and other attributes available through FND_
REQUEST package.
As you can see, upon successful submission of the concurrent program, we raise a confirmation
exception with the request ID.
Summary
The last step that is deliberately not covered in this article is using the Chain of Command design
pattern to chain the Java concurrent program with a SQL*Loader process to upload the comma
delimited file into the database for further processing.
Please, read about this design pattern in our previous posts and articles.
The Chain of Command implementation described gives you a powerful tool to declaratively define
post processor to concurrent programs. You can choose to hard code the calls inside your existing
code, and submit SQL*Loader concurrent program right from the Java concurrent program that
prints out the uploaded file.
This is our goal to show the ways and let you choose the one fit for you.