Friday, July 20, 2012

Emailing image via PLSQL


Today's example is a practical oldie on working with binary files in PLSQL.  I recently had a requirement to attach a file to an email out of Concurrent Manager but the file could be one of several types.  I frequently generated flat files as attachments but had never attached images to an email.  Additionally, my previous examples would work with files that were stored in database tables as BLOBS.  This article will explain the following two items:

1. create a temporary LOB from a BFILE
2. attach LOB to an email as a raw datatype

First, create a database directory that will contain the BLOB you want to attach to an email.  In this example we will use a .jpg of my dog, Layla:


Next, I will FTP my file to the database tier to /usr/tmp:



The setup of utl_smtp is not covered in this topic - the author assumes your DBA configured this correctly and it is available for use.  Below is the complete script to build and send the image as an attachment via utl_smtp.  We'll discuss each part of it below after we run it and see the results:


set serveroutput on size 1000000
declare
--
lv_oracle_directory            varchar2(240)        := '/usr/tmp';
lv_file_name                   varchar2(240)        := 'layla.jpg';
lv_smtp_host                   varchar2(240)        := '192.168.X.X';
lv_list                        varchar2(240)        := 'oracle-hack@hackity.com';
--
m_mail_conn                    utl_smtp.connection;
m_crlf                         varchar2(2)          := utl_tcp.crlf; -- carriage return linefeed
m_boundary                     varchar2(60)         := '6a_j9dh3sk2lao9vdse830';
m_first_boundary               varchar2(60)         := '--'|| m_boundary || m_crlf;
m_last_boundary                varchar2(60)         := '--'|| m_boundary || '--' || m_crlf;
--
lb_lob                         blob;
ln_amount                      number;
ln_position                    number;
lr_raw                         raw(2000);
--
l_bfile_source_location        bfile;
li_size                        integer;
i                              pls_integer;
li_length                      pls_integer;
--
ln_max_base64_line_width       constant pls_integer := 76 / 4 * 3;
--
begin
   --
   m_mail_conn := utl_smtp.open_connection(lv_smtp_host, 25);
   --
   utl_smtp.helo(m_mail_conn, lv_smtp_host);
   utl_smtp.mail(m_mail_conn, 'Oracle_eBiz@hackity.com'); -- needs to look and feel like an email addy with '@' and '.'
   utl_smtp.rcpt(m_mail_conn,lv_list);
   utl_smtp.open_data(m_mail_conn);
   --
   -- MIME header
   --
   utl_smtp.write_data(m_mail_conn,'MIME-Version: 1.0' || utl_tcp.crlf);
   utl_smtp.write_data(m_mail_conn, 'Content-Type: multipart/mixed; boundary="'||m_boundary||'"'||m_crlf);
   utl_smtp.write_data(m_mail_conn, 'Subject: '||'Test attachment'||m_crlf||m_crlf);
   --
   -- email body text
   --
   utl_smtp.write_data(m_mail_conn, m_first_boundary);
   utl_smtp.write_data(m_mail_conn, 'Content-Type: '||'text/plain'||m_crlf);
   utl_smtp.write_data(m_mail_conn, ' charset=US-ASCII'||m_crlf);
   utl_smtp.write_data(m_mail_conn,'Hello, world...see the attached file.'||m_crlf);
   --
   -- first attachment MIME header creation
   --
   utl_smtp.write_data(m_mail_conn, m_first_boundary);
   utl_smtp.write_data(m_mail_conn, 'Content-Type: "image/jpeg"; name="'||lv_file_name||'"'||m_crlf);
   utl_smtp.write_data(m_mail_conn, 'Content-Transfer-Encoding: base64'||m_crlf);
   utl_smtp.write_data(m_mail_conn, 'Content-Disposition: attachment; filename="'||lv_file_name||'"'||m_crlf);
   --
   -- create a temporary LOB (versus storing in a table)
   --
   dbms_lob.createtemporary(lb_lob, true, dbms_lob.session);
   l_bfile_source_location := bfilename('GVS_DOG', 'layla.jpg');
   li_size := dbms_lob.getlength(l_bfile_source_location);
   --
   dbms_output.put_line('li_size: '||li_size);
   dbms_lob.open(l_bfile_source_location);
   --
   dbms_lob.loadfromfile(dest_lob => lb_lob                  -- IN OUT NOCOPY  CLOB CHARACTER SET ANY_CS,
                        ,src_lob  => l_bfile_source_location -- IN             BFILE,
                        ,amount   => li_size);               -- IN             INTEGER,
   --
   i := 1;
   li_length := dbms_lob.getlength(lb_lob);
   dbms_output.put_line('length li_length: '||li_length);
   --
   while (i < li_length) loop
      if(i + ln_max_base64_line_width < li_length)then
         utl_smtp.write_raw_data (m_mail_conn
                                 ,utl_encode.base64_encode(dbms_lob.substr(lb_lob, ln_max_base64_line_width, i)));
      else
         utl_smtp.write_raw_data(m_mail_conn
                                ,utl_encode.base64_encode(dbms_lob.substr(lb_lob, (li_length - i),  i)));
      end if;
      utl_smtp.write_data(m_mail_conn, utl_tcp.crlf);
      i := i + ln_max_base64_line_width;
   end loop;
   --
   utl_smtp.write_data(m_mail_conn, m_crlf);
   utl_smtp.write_data(m_mail_conn, m_last_boundary);
   utl_smtp.write_data(m_mail_conn, m_crlf);
   utl_smtp.close_data(m_mail_conn);
   utl_smtp.quit(m_mail_conn);
   --
   dbms_lob.close(l_bfile_source_location);
   dbms_lob.freetemporary(lb_lob);
   commit;
   --
exception
   when others then
      dbms_output.put_line('Error: '||sqlerrm);
      rollback;
end;
/

Save this off as utl_smtp and run it from SQL*Plus:



A few seconds later there is a package:



Now that we know this works, let's take a brief look at some of the script:





  1. lv_smtp_host - this needs to be defined by your mail people.  I have sometimes seen network aliases created so you can use mail.hostname.com in case the IP address changes
  2. The recipient is hard-coded as one value here and must be a valid email address format or you'll potentially see the error Error: ORA-29279: SMTP permanent error: 501 5.1.3 Bad recipient address syntax
  3. m_boundary - this arbitrary value is used throughout the mail message you build to separate each section.  The value should not exist anywhere in the mail message.  The initial boundary starts with two dashes and the last boundary of the email ends with two dashes.
  4. I copied this formula from several examples I found on the webernets.  The first time I tried to attach the file by breaking it up into 1000 byte chunks it came through garbled.  Not understanding this very well I then went to RFC 2045 (Multipurpose Internet Mail Extensions) for an explanation that states the file must be transferred in base64 encoding and also must be chunked into pieces no larger than 76 characters not including the carriage-return line feed.
  5. This address also nees to be in a valid format or you'll see error Error: ORA-29279: SMTP permanent error: 501 5.1.7 Bad sender address syntax
  6. Because this message is plain text as well as base64 you use this particular content-type  This is also where you specifiy what the boundary string will be but do not use any dashes in front of it.
  7. The body of the email is created using a plain-text content-type and an ASCII character set.  This is also where you specify the first boundary and it includes the "--"
  8. The same boundary is used but repeated here so separate the body from the attachment.  base64 is specified as the encoding as well as the attachment and filename.
  9. This line creates a temporary lob.  Previously I would pull LOBs out of tables using a SQL statement or procedure.  In this example we want to grab it from the filesystem for use as an attachment.  This line creates an empty lob ready to accept the file
  10. This will assign the file to the empty lob
  11. write the image attachment in 76 byte chunks







Tuesday, April 24, 2012

Launching XML Publisher from OA Framework

Today's example shows how to generate an XML Publisher report from OA Framework. It uses the delivered DocumentViewer bean to invoke an XML Pub report by clicking a button, instead of running a Concurrent Manager program. Rather than just create a "Hello, world" example, we'll embed our custom report in a SSHR function to show how to feed in a parameter from the page.
Lettuce use the Personal Information screen in SSHR to add a button where an employee can generate an Employment Verification letter. I'm a techie and not a generalist so this might not be a great real-world HR example but it is meant to showcase embedding an XML Publisher report inside OAF.
First, lettuce create an XML Publisher report that contains an employee's basic person, assignment and salary information. I like to avoid Oracle Reports altogether so I build my own Data Definition as follows:

Save the above as an .xml file and upload it as an XML Publisher Data Definition by browsing to the following:
(N) XML Publisher -> Data Definitions -> Create
Configure as follows, browsing for your newly created .xml file:

While the point of this exercise is to create and XML Publisher report launched from OA Framework and not from Concurrent Manager, I will also create a Concurrent Program to demonstrate that the report can be launched from two places in Oracle Applications. I also will use the generated XML to create the XML Template for the letter. Here is the configuration of my Concurrent Program that calls XDODTEXE:


Prior to running this report we need to create a PLSQL package that will transfer the P_PERSON_ID parameter into the Data Definition. Copy and paste:
create or replace package gvs_emp_verif_pkg as
--
p_person_id number;
--
gc_person_id varchar2(300);
--
function before_report return boolean;
--
end gvs_emp_verif_pkg;
/
show errors;
create or replace package body gvs_emp_verif_pkg as
--
function before_report return boolean is
--
begin
--
gc_person_id := ' and papf.person_id = '||p_person_id;
--
return true;
--
end before_report;
--
end gvs_emp_verif_pkg;
/
show errors;
Compile these as APPS and then run the report...

After running the report you'll save this output as .xml and construct your .rtf template. Only a summary of the steps necessary for merging an .xml file with a template are shown here. For an end-to-end example, see the previous article "Using XML Publisher without Report Builder":
This is the draft template I will load into XML Publisher:

Browse to (N) XML Publisher -> Templates -> Create Template and configure as follows:

Here's how the letter looks when combining the XML with the template.

Our goal is to get this to launch out of an OA Framework page rather than a Concurrent Program. Here are the steps:
1. Create a custom OA Framework page that extends delivered /oracle/apps/xdo/oa/common/webui/DocumentViewerRn.MainRegion
2. Associate a Controller .class and Application Module to this custom page that invokes method "getXMLData"
3. Create an FND Form Function for the custom page
3. Use personalizations to create a button that points at the FND Form Function
One step at a time, lettuce create our page in JDeveloper:

Right-click your MainRN and add a new region. Populate the "Extends" property with the value /oracle/apps/xdo/oa/common/webui/DocumentViewerRn.MainRegion. You will see the region information populate and be grayed-out since it is delivered by Oracle.

Right-click your MainRN and choose Set New Controller so you can write a few lines of Java code that will pass the person id to the report.

If your JDeveloper application runs as slow as mine, leave this simmer for a few minutes while you personalize (configure) a button in Self-Service HR that will invoke this Page and Controller.
(N) Employee Self-Service -> Personal Information

In the above case I created my own custom responsibility, menus and functions. Click the personalize link in the upper right hand corner:

Click the Create icon next to the cell that has the Update button:

Configure as follows:

That configuration will call our custom page and OA Framework code. Since we haven't developed nor deployed that, return to JDeveloper to complete it. Recall that we have already created a page that has just the delivered DocumentViewer bean and we set a custom Controller. Here is how you should write your Controller code:

Here's the text in case you'd like to copy/paste. I removed some white-space so it would fit on the screen print above.
package hack.oracle.apps.per.hack.webui;
import oracle.apps.fnd.common.VersionInfo;
import oracle.apps.fnd.framework.webui.OAControllerImpl;
import oracle.apps.fnd.framework.webui.OAPageContext;
import oracle.apps.fnd.framework.webui.beans.OAWebBean;
import oracle.apps.fnd.framework.OAApplicationModule;
import oracle.jbo.domain.BlobDomain;
import oracle.apps.xdo.oa.common.DocumentHelper;
import java.io.*;
public class gvsDocCO extends OAControllerImpl
{
public static final String RCS_ID="$Header$";
public static final boolean RCS_ID_RECORDED =
VersionInfo.recordClassVersion(RCS_ID, "%packagename%");
/**
* Layout and page setup logic for a region.
* @param pageContext the current OA page context
* @param webBean the web bean corresponding to the region
*/
public void processRequest(OAPageContext pageContext, OAWebBean webBean)
{
super.processRequest(pageContext, webBean);
pageContext.writeDiagnostics(this,"GVS - Version 2.0",3);
String strPersonId = pageContext.getParameter("PersonId");
pageContext.writeDiagnostics(this,"GVS - strPersonId: " + strPersonId,3);
//
OAApplicationModule oaAM = pageContext.getApplicationModule(webBean);
//
pageContext.putParameter("p_DataSource",DocumentHelper.DATA_SOURCE_TYPE_BLOB);
pageContext.putParameter("p_DataSourceCode","GVS_EMP_VERIF");
pageContext.putParameter("p_DataSourceAppsShortName","PER");
pageContext.putParameter("p_TemplateCode","GVS_EMP_VERIF");
pageContext.putParameter("p_TemplateAppsShortName","PER");
pageContext.putParameter("p_Locale","Default");
pageContext.putParameter("p_Locale","English:United States");
pageContext.putParameter("p_OutputType","PDF");
pageContext.putParameter("p_XDORegionHeight","200%");
Serializable[] oaParams = {strPersonId};
BlobDomain result = (BlobDomain)oaAM.invokeMethod("getGVSData",oaParams);
pageContext.putSessionValueDirect("XML_DATA_BLOB", result);
}
public void processFormRequest(OAPageContext pageContext, OAWebBean webBean)
{
super.processFormRequest(pageContext, webBean);
}
}
Notice that I am doing all of the code in the processRequest. This event fires when the page loads as opposed to pushing a button to generate the document. I want to click a button, launch the page and have the document launch automatically. If you would like the document creation to occur only if you press a button on your custom page just move the last two lines into the processFormRequest.
Also notice that I set the default as "PDF" using this line:
pageContext.putParameter("p_OutputType","PDF");
If you include a default output type you will not get a drop-down from Oracle that allows you to choose a different document type. I will show how it looks both ways later. For now, continue your Jdeveloper hacking by creating an Application Module.
Right-click your project and choose New Application Module...

Here's what it looks like in JDeveloper:

And the text for copy/paste purposes:
package hack.oracle.apps.per.hack.server;
import oracle.apps.fnd.framework.server.OAApplicationModuleImpl;
import oracle.apps.fnd.framework.OAException;
import oracle.apps.fnd.framework.server.OADBTransactionImpl;
import oracle.apps.fnd.framework.server.OADBTransaction;
import oracle.jbo.domain.BlobDomain;
import oracle.apps.xdo.oa.util.DataTemplate;
import java.sql.SQLException;
import oracle.apps.xdo.XDOException;
import com.sun.java.util.collections.Hashtable;
public class gvsPubAMImpl extends OAApplicationModuleImpl {
/**This is the default constructor (do not remove)
*/
public gvsPubAMImpl() {
}
/**Sample main for debugging Business Components code using the tester.
*/
public static void main(String[] args) {
launchTester("hack.oracle.apps.per.hack.server", /* package name */
"gvsPubAMLocal" /* Configuration Name */);
}
public BlobDomain getGVSData (String strPersonId)
{
BlobDomain blobDomain = new BlobDomain();
try
{
DataTemplate datatemplate = new DataTemplate(((OADBTransactionImpl)getOADBTransaction()).getAppsContext(), "PER", "GVS_EMP_VERIF");
Hashtable parameters = new Hashtable();
parameters.put("P_PERSON_ID",strPersonId);
datatemplate.setParameters(parameters);
datatemplate.setOutput(blobDomain.getBinaryOutputStream());
datatemplate.processData();
}
catch(SQLException e)
{
throw new OAException("SQL Error=" + e.getMessage(),OAException.ERROR);
}
catch (XDOException e)
{
throw new OAException("XDOException" + e.getMessage(),OAException.ERROR);
}
catch(Exception e)
{
throw new OAException("Exception" + e.getMessage(),OAException.ERROR);
}
return blobDomain;
}
}
Notice the line "new DataTemplate" needs your Data Template code as the second parameter; the first parameter is the application short name, in my case I used "PER" for Human Resources. Also, any parameters can be supplied using a Hashtable. Remember that my Concurrent Program has one parameter defined and I called the token "P_PERSON_ID". This must match here when supplying a parameter to it. That's it as far as coding goes. In order to deploy this to Oracle Applications you must perform the following:
1. Make and Rebuild your project in JDeveloper
2. .zip the project on your computer and FTP it to $JAVA_TOP
3. Unzip the project in $JAVA_TOP
4. Import the custom page into the database
5. Bounce apache and OA Core
There are two different ways to import your custom page. One is to use jpximport on your machine. The other is to use the XML Importer on the middle tier. Both work fine; I used the jpximport locally using this command:
C:\JDEVOAFR12\jdevbin\oaext\bin\import C:\JDEVOAFR12\jdevhome\jdev\myclasses\hack\oracle\apps\per\hack\webui\gvsPubPG.xml -rootdir C:\JDEVOAFR12\jdevhome\jdev\myclasses -mmddir "C:\JDEVOAFR12\jdevbin\oaext\config\mmd" -username "apps" -password "password123" -dbconnection "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=test.hack.com)(PORT=1521))(CONNECT_DATA=(SID=HACK)))"
Once you have completed steps 1-5, log in and press the button you configured earlier:

If you press the Export button in the above picture you can download the file:

Recall that I defaulted the document to open automatically and specified PDF. If you comment out this line:
pageContext.putParameter("p_OutputType","PDF");
You will have the choice to choose HTML, PDF, Excel or RTF:

What's it look like in HTML?

Option to download RTF:

Excel:

Tuesday, July 5, 2011

Using XML Publisher without Report Builder

Oracle Applications created XML Publisher (also known as BI Publisher) to allow more flexibility and aesthetics whilst creating new reports or changing the layout of existing reports. In order to create an "XML Publisher Report" originally, one was required to use Reports Builder. If you've ever dreamed of creating a professional looking report for your user(s) but the thought of opening Report Builder made you cry this article is for you.

Using a Data Template you can define a report based off of plain ole SQL. If necessary you can write your own Before Report and After Report triggers as well using a plain ole PLSQL package. Here are the components of an XML Publisher report without you'll need without the interference of Report Builder:

  1. Concurrent Program pointing at XDODTEXE
  2. SQL statement inside a Data Definition (copy and paste a delivered one)
  3. PLSQL package (to do any before report manipulation of parameters)
  4. XML Publisher template

Lettuce start small with a simple SQL statement that retrieves some people, their addresses and their organization:


select ppx.employee_number
,ppx.full_name
,haou.name
,pa.address_line1
,pa.address_line2
,pa.address_line3
,pa.region_1
,pa.region_2
,pa.town_or_city
from per_people_x ppx
,per_addresses pa
,per_assignments_x pax
,hr_all_organization_units haou
where ppx.person_id = pa.person_id
and ppx.person_id = pax.person_id
and pax.organization_id = haou.organization_id
and haou.name in ('Hack Developers','Disorganization')
order by 2 asc


Our objective is to take this SQL and use it as the basis for our new report. We'll parameterize the organization name to a comma-separated list of Organizations. The first step is to manually create a Concurrent Program as shown below which points at the Concurrent Executable XDOTEXE:



For purposes of our example, let's use a free text field to enter our list of Organizations as a parameter. I've staged some data into test organizations and we'll default this to the following constant value: 'Hack Developers','Disorganization'. Note that the Token field is required. Remember what you enter here as we'll use the name in our XML Data Definition.



Right now, this Concurrent Program is expecting to run and find an associated data template from XML/BI Publisher but it will break because there is nothing mapped to HACK_ADDRESS_REPORT. Before pointing at our data definition we must take a detour and do the following:

  1. Create a Data Definition
  2. Creating an XML Publisher template

I recommend copying/pasting from a delivered Data Definition such as the AP Open Balances Revaluation Report. To do this go to the following:

(N) XML Publisher Administrator -> Data Definitions

Search for APOBRR and follow the screen prints to download it:




The definition we chose to copy has a lot more going on than what we need to get a small example working so I've taken the liberty of modifying it here for our example. Lettuce troll through this and try to make cents of it:



The blog software had consternation parsing the various XML tags so I couldn't provide a straight up copy/paste. Click the link to visit Google Docs and save the text of the above screen print as a text document:
Click here for the XML Data Definition

  1. If you need to do any PLSQL programming specify the package name here
  2. This is the token defined in the Concurrent Program shown above
  3. Q_ORG_SELECT is an arbitrary name given to the query. You can have any number of queries
  4. The &gc_where_clause is the output parameter from our PLSQL package
  5. This defines a Before Report trigger that executes prior to the SQL statement. (The trigger name stays the same, just change the source to your PLSQL package)

You must define an arbitrary name for each element=name"" tag. However, each of the value attributes must match to a SQL column.

Save this as HACK_ADDRESS_REPORT.xml. We are ready to upload it into XML Publisher by doing the following:

(N) XML Publisher Administrator -> Data Definitions

Click "Create Data Definition"



The name is arbitrary but the code must match the Concurrent Program short name. I use HACK_ADDRESS_REPORT for both. Click Apply.


The next screen is where you upload the Data Definition and assign a layout. For right now we are only going to upload the Data Definition to tell Oracle to get the data we've asked for. We can then run the report and get actual XML output. We can use that output when creating our layout template.

Upload, Browse, Apply, Confirmation:









We've told the report to use our parameter but have not yet developed the PLSQL package. The P_ORGANIZATION_LIST parameter needs to go into the package header. This accepts the variable and makes it available for manipulation. Additionally, we must define the &gc_where_clause parameter in the package header and assign it some kind of value so the SQL is processed successfully. Here is the package header:

create or replace package hack_address_report_pkg as
--
p_organization_list varchar2(240) := null;
gc_where_clause varchar2(900) := null;
--
function before_report return boolean;
--
end hack_address_report_pkg;


And here is the package body. I've included a couple of functions to remove duplicate and trailing commas should the user have difficulty entering Organization names separated by comma:

create or replace package body hack_address_report_pkg as
--------------------------------------------------------------------------------
-- remove_trailing_character
--
-- removes a duplicate character from a string
--------------------------------------------------------------------------------
function remove_duplicate_character (p_string varchar2
,p_char_dup_to_remove varchar2) return varchar2 is
--
lv_string varchar2(4000) := p_string;
--
begin
--
hr_utility.set_location('Entering remove_duplicate_character...',5);
loop
--
lv_string := replace (lv_string,p_char_dup_to_remove,',');
if instr(lv_string,p_char_dup_to_remove) = 0 then
exit;
end if;
--
hr_utility.set_location(lv_string,35);
--
end loop;
--
hr_utility.set_location('lv_string: '||lv_string,90);
return lv_string;
--
exception
when others then
hr_utility.set_location('Error: '||sqlerrm,999);
return p_string;
end remove_duplicate_character;
--------------------------------------------------------------------------------
-- remove_trailing_character
--
-- removes character from end of string if it exists
--------------------------------------------------------------------------------
function remove_trailing_character (p_string varchar2
,p_char varchar2) return varchar2 is
--
lv_return varchar2(4000) := null;
--
begin
--
hr_utility.set_location('p_string: '||p_string,5);
hr_utility.set_location('p_char: '||p_char,10);
hr_utility.set_location('substr(p_string,-1): '||substr(p_string,-1),20);
--
if (substr(p_string,-1) = p_char ) then
lv_return := substr(p_string,1,length(p_string) -1);
else
lv_return := p_string;
end if;
--
hr_utility.set_location('lv_return: '||lv_return,50);
return lv_return;
--
exception
when others then
hr_utility.set_location('p_string: '||p_string,999);
return p_string;
end remove_trailing_character;
--------------------------------------------------------------------------------
-- before_report
--------------------------------------------------------------------------------
function before_report return boolean is
--
lv_working_string varchar2(4000) := null;
--
begin
--
fnd_file.put_line(fnd_file.log,'Here 10');
--
if p_organization_list is not null then
--
lv_working_string := remove_duplicate_character (p_organization_list,',,');
lv_working_string := remove_trailing_character (lv_working_string,',');
--
gc_where_clause := ' and haou.name in ( ' || lv_working_string || ') ';
else
gc_where_clause := ' and 1 = 2';
fnd_file.put_line(fnd_file.log,'No Organization parameter(s) entered.');
end if;
--
fnd_file.put_line(fnd_file.log,'Here 900');
return true;
--
end before_report;
--
end hack_address_report_pkg;
/
show errors;


Focus your attentions on the before_report function as that one will assign a WHERE clause if the parameter is populated from the Concurrent Program. If it is not populated I set the WHERE clause to 1 = 2 so no records are returned yet the program will not die. The fnd_file messages can be viewed in the "View Log". Also note that the function must return a boolean value of true for the report to continue.

Let's run the program and see the output. Assign the Concurrent Program to a Request set prior to running it. I'll use US SHRMS Reports & Processes:

(N) System Administrator -> Security -> Responsibility -> Requests

Query US SHRMS Reports & Processes.
Add a new record.
Choose Hack Address Report.
Save.



Change Responsibility to US Super HRMS Manager

Run it:


When it completes click View Output and you will see the XML Output. Save this as HACK_ADDRESS_REPORT_output.xml. We will use it to create an XML Template and arrange the output:


Notice the number of errors in my Concurrent Manager. This is because I included a semi-colon in my Data Definition that resulted in "java.sql.SQLException: ORA-00911: invalid character". Remove this and you won't see the error:




At this point we have generated XML data and we have an opportunity to tranform it into something prettier by merging it with an XML Template. To do this you must install BI Publisher Template Builder for Word. I used version "10.1.3.4.1" for this example. Once Bee Eye Publisher is installed you can open word and begin your layout. I started by inserting a two dimensional tableau with column headings. The plan is to insert the XML data tags into the table and bracket them inside a for-each loop. The initial layout is show below:


In order to merge the XML data we created with this template and easily drag-drop the tags in place you must perform the following:

Oracle BI Publisher -> Data -> Load Sample XML Data

...and choose the HACK_ADDRESS_REPORT_output.xml initially generated from Concurrent Manager.


The dater has been loaded successully:



Oracle BI Publisher -> Insert -> Field



Drag each field from the field browser into the table.


After the table is complete highlight the row and choose:

Oracle BI Publisher -> Insert -> Repeating Group:



Take the defaults; G_TRANSACTION will match the tag defined in the Data Definition The name is arbitrary and you could change it to something else. You might have separate sections associated with separate queries for the for header and lines:



You'll notice the "F" and "E" tags added around the row to denote "For" and "Each". Save this as a .rtf file.


Upload it into XML Publisher as a template with the same short name so that is associated with the Data Definition automagically:

XML Publisher -> Templates -> Create Template -> Browse for your .rtf:


Click Apply.

At this stage of the game you'll probably have that same feeling one gets when nearing the bottom of a box of Cracker Jacks and you're almost ready for the prize. There is one last step, however, and you must change the output type of your Concurrent Request from Text to XML so rather than tags you get a formatted report:


Run the report and then click View Output:


Happy hacking.