Monday, December 28, 2009

Scrolling Table :Table with horizontal Scrollbars and vertical scrollbars

Hi All,
I hope this is quite an interesting reading for most developers and they will try to put this feature in OAF tables.This is a very essential requirement in any of the custom OAF pages, where you show data in OATableBean and table has lot of columns, so, user has to scroll the entire page from scroll bar which is automatically rendered by browser if table width is more than page width.This is very painful for user as everytime, he scrolls the scroll-bar to see extra table columns,entire page is scrolled and he has to scroll back to the page to see other data.Similary, if you are displaying more than 10-20 rows in the table , user needs to scroll the entire page to see all the rendered rows of table.

In order to lessen the pain of the user and to have better UI design, we will render scroll bars both horizontal and vertical just surrounding the table
as , shown in the image below :


So, now lets focus how can we bring these scroll bars. In that case you can use the DIV tag to do the job for you.Put 2 RawText Bean(named DivStart, DivEnd), before and after the Table Bean in the JRAD page as shown in the image below IN JDEVELOPER:



You can use following API in process request :
public void processRequest(OAPageContext pageContext, OAWebBean webBean)
{
super.processRequest(pageContext, webBean);
//SEE ALL PARAMETER values you need to pass in the java comments of the method
addScrollBarsToTable(pageContext, webBean,"DivStart","DivEnd",true,"400",true,"400");
.
.
}


/**
* @param pageContext the current OA page context.
* @param webBean the web bean corresponding to the region.Pass the webBean of pagelayout.
* @param preRawTextBean is name of the Rawtext bean just above the table bean.
* @param postRawTextBean is name of the Rawtext bean just below the table bean.
* @param horizontal_scroll is boolean value if horizontal scrollbar is required/not required.
* @param width is the minimum width of table beyond which horizontal scrollbar is displayed.i.e
* if width is greater than this horizontal scrollbar is displayed.If this is passed as null, defalt value
* is considered which is 400.
* @param vertical_scroll is boolean value if vertical scrollbar is required/not required.
* @param height is the minimum height of table beyond which horizontal scrollbar is displayed .i.e
* if width is greater than this horizontal scrollbar is displayed.If this is passed as null, defalt value
* is considered which is 400.
*/
public void addScrollBarsToTable(OAPageContext pageContext,
OAWebBean webBean,
String preRawTextBean,
String postRawTextBean,
boolean horizontal_scroll, String width,
boolean vertical_scroll, String height)
{
String l_height = "400";
String l_width = "400";
pageContext.putMetaTag("toHeight",
"<style type=\"text/css\">.toHeight {height:24px; color:black;}</style>");

OARawTextBean startDIVTagRawBean =
(OARawTextBean) webBean.findChildRecursive(preRawTextBean);
if (startDIVTagRawBean == null)
{
throw new OAException("Not able to retrieve raw text bean just above the table bean. Please verify the id of pre raw text bean.");
}

OARawTextBean endDIVTagRawBean =
(OARawTextBean) webBean.findChildRecursive(postRawTextBean);
if (endDIVTagRawBean == null)
{
throw new OAException("Not able to retrieve raw text bean just below the table bean. Please verify the id of post raw text bean.");
}

if (!((height == null) || ("".equals(height))))
{
try
{
Integer.parseInt(height);
l_height = height;
}
catch (Exception e)
{
throw new OAException("Height should be an integer value.");
}
}


if (!((width == null) || ("".equals(width))))
{
try
{
Integer.parseInt(width);
l_width = width;
}
catch (Exception e)
{
throw new OAException("Width should be an integer value.");
}
}

String divtext = "";
if ((horizontal_scroll) && (vertical_scroll))
{
divtext =
"<DIV style='width:" + l_width + ";height:" + l_height + ";overflow:auto;padding-bottom:20px;border:0'>";
}
else if (horizontal_scroll)
{
divtext =
"<DIV style='width:" + l_width + ";overflow-x:auto;padding-bottom:20px;border:0'>";
}
else if (vertical_scroll)
{
divtext =
"<DIV style='height:" + l_height + ";overflow-y:auto;padding-bottom:20px;border:0'>";
}
else
{
throw new OAException("Both vertical and horizintal scrollbars are passed as false,hence, no scrollbars will be rendered.");
}
startDIVTagRawBean.setText(divtext);
endDIVTagRawBean.setText("</DIV>");
}
Please Note : Height and Width passed in addScrollBarsToTable API are the minimum
height and width of the table respectively which will be displayed on the page. Hence if this display resolution is available on page the scrollbars will not be displayed or corresponding one scrollbar or both scrollbars will be displayed.If you are not able see both scrollbars, even if you pass true for both in this method, try to put less resolution like 150 and 150 etc.

Happy coding..!

Friday, December 18, 2009

Blocking User on submit Action in a OAF page

This is one of the most frequently required feature in OAF page, in certain conditions. Basically our requirement is to block user from pressing a button twice, i.e.,if a button is pressed for the first time,till the time request processing is going on for the first request on server either the button should be disabled on page or an hourglass should be shown on the page, so that user should not be able to press it again to start another request, before this request's response has been received from server. This is very essential feature where

1) You are lets say creating a account.
2) Doing a transaction.
3) Generating a report.
4) Doing a transaction which takes significant amount of time which can make user impatient and press the button again.

The standard solution for this provided by OAF is setBlockOnEverySubmit API.Whenever a submit action takes place on a page, subsequent submits can be blocked.
When using a blocking on submit technique, when the submit action takes place, the cursor becomes busy and prevents any other submit action until the current submit event has been handled.
The block on submit behavior is not enabled by default on a page. However, For Partial Page Refresh (PPR) events alone, it is enabled by default.
To implement the block on submit behavior on a specfic page, add the following code to the processRequest() of that page CO:

import oracle.apps.fnd.framework.webui.beans.OABodyBean;

public void processRequest(OAPageContext pageContext, OAWebBean webBean)
{
super.processRequest(pageContext, webBean);
OAWebBean body = pageContext.getRootWebBean();
if (body instanceof OABodyBean)
{
((OABodyBean)body).setBlockOnEverySubmit(true);
}
...
...
}

But there are exceptions to this API functionality, what if my page has multiple buttons and I want this nature only on press of one of the button.Situations like this the standard solution is using OAProcessing page(See dev guide for OAProcessing page implementation, its explained well!), which is typically used in long running processed.But what if user is not ready to leave the page at all and asks you to block the navigation somehow? What if you are mobile browser, where OAProcessing page does not work fine. Situations like this can be taken care through javascript.
I must repeat again, use this javascript solution only when your requirement is not fulfilled by standard setBlockOnEverySubmit API provided by framework.Ok here are the steps for the javascript solution, which will block the action by making the button disabled as soon as it is pressed and releasing it as soon as your action is finished :

1) Add a button bean , instead of submit button bean. Lets say its id is "item1" in property inspector. Set destination uri property in property inspector as :
javascript:document.getElementById('item1').disabled=true;submitForm('DefaultFormName',0,{'XXX':'Y'});

2) In process form request, you can catch the event as :

public void processFormRequest(OAPageContext pageContext,
OAWebBean webBean)
{
super.processFormRequest(pageContext, webBean);

.// the js button click event
// don't use pageContext.getParameter() API to get parameter values
// in R12 because its doing validation on JS paremeters.
// so,instead of getting parameter value from pagecontext
//getting directly from http request.
if(pageContext.getRenderingContext()
.getServletRequest().getParameter("XXX")!=null)
{
// ur button event logic in process form request
......

//remove this parameter from http request
pageContext.removeParameter("XXX");
}
}

I hope this helps to people which are facing such situations. Happy coding..!

Tuesday, December 15, 2009

Migration of AOL setups from one instance to Another.

Hi All,
Typically this is the requirement of every Apps project whether its a upgrade/implementation/support. You might create AOL objects like profiles,alerts,AK regions etc. , here I am listing all the LDT commands which will help you in migration.LDT download command extract the AOL object in a .ldt file and then u can put that in target instance and call upload LDT command to upload it in target instance.
I think there are numerous websites where this is listed, but still commands like ldt of alert, ak region is still hard to find. Hence, I am consolidating all of them at one place :

function scripts
------------------
$FND_TOP/bin/FNDLOAD apps/apps 0 Y DOWNLOAD $FND_TOP/patch/115/import/afsload.lct XXADAT_MAT_REQ_HIST.ldt FUNCTION FUNCTION_NAME=XXADAT_MAT_REQ_HIST

$FND_TOP/bin/FNDLOAD apps/apps 0 Y UPLOAD $FND_TOP/patch/115/import/afsload.lct XXADAT_MAT_REQ_HIST.ldt


responsibility scripts
------------------------
FNDLOAD apps/apps O Y DOWNLOAD $FND_TOP/patch/115/import/afscursp.lct XXADAT_PROD_TIME_BOOKING.ldt FND_RESPONSIBILITY RESP_KEY="XXADAT_PROD_TIME_BOOKING"

$FNDLOAD apps/apps O Y UPLOAD $FND_TOP/patch/115/import/afscursp.lct file_name.ldt


menu scripts
-------------
FNDLOAD apps/apps O Y DOWNLOAD $FND_TOP/patch/115/import/afsload.lct AHL_PRD_MBENCH_USER_SUB_MENU.ldt MENU MENU_NAME="AHL_PRD_MBENCH_USER_SUB_MENU"

$FNDLOAD apps/apps O Y UPLOAD $FND_TOP/patch/115/import/afsload.lct file_name.ldt

lookup scripts
--------------
FNDLOAD apps/apps 0 Y DOWNLOAD $FND_TOP/patch/115/import/aflvmlu.lct AHL_PRD_DEFERRAL_TYPE.ldt FND_LOOKUP_TYPE APPLICATION_SHORT_NAME ="XXADAT" LOOKUP_TYPE="AHL_PRD_DEFERRAL_TYPE"

FNDLOAD apps/apps 0 Y UPLOAD $FND_TOP/patch/115/import/aflvmlu.lct XXADAT_NO_CONCURRENT_LOGIN_WOS.ldt

ak region download script
-------------------------------
java oracle.apps.ak.akload apps apps THIN "(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(Host = < instance IP > )(Port = 1541)) (CONNECT_DATA = (SID=DEV)))" DOWNLOAD XXADAT_EM_LOV.ldt GET CUSTOM_REGION AHL XXADAT_EM_LOV

ak region upload script
------------------------
java oracle.apps.ak.akload apps apps THIN "(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(Host = < instance IP > )(Port = 1620)) (CONNECT_DATA = (SID=RTST)))" UPLOAD XXADAT_EM_LOV.ldt UPDATE CUSTOM_REGION

profile scripts
---------------
FNDLOAD apps/apps O Y DOWNLOAD $FND_TOP/patch/115/import/afscprof.lct XXADAT_BUDGET_MANHOURS_NROUTINE.ldt PROFILE PROFILE_NAME="XXADAT_BUDGET_MANHOURS_NROUTINE" APPLICATION_SHORT_NAME="XXADAT"

FNDLOAD apps/apps O Y UPLOAD $FND_TOP/patch/115/import/afscprof.lct XXADAT_BUDGET_MANHOURS_ROUTINE.ldt

message scripts
----------------
FNDLOAD apps/apps 0 Y DOWNLOAD $FND_TOP/patch/115/import/afmdmsg.lct XXADAT_MIN_EXP_MSG.ldt FND_NEW_MESSAGES APPLICATION_SHORT_NAME="XXADAT" MESSAGE_NAME="XXADAT_MIN_EXP_MSG"

FNDLOAD apps/apps 0 Y UPLOAD $FND_TOP/patch/115/import/afmdmsg.lct XXADAT_MIN_EXP_MSG.ldt

CONCURRENT PROGRAM
--------------------
FNDLOAD apps/appsrtst O Y DOWNLOAD $FND_TOP/patch/115/import/afcpprog.lct XXADAT_CL_COMP_WO_F_VISIT.ldt PROGRAM APPLICATION_SHORT_NAME="XXADAT" CONCURRENT_PROGRAM_NAME="XXADAT_CL_COMP_WO_F_VISIT"

FNDLOAD apps/apps O Y UPLOAD $FND_TOP/patch/115/import/afcpprog.lct XXADAT_CL_COMP_WO_F_VISIT.ldt

alert
------------
FNDLOAD apps/apps 0 Y DOWNLOAD $ALR_TOP/patch/115/import/alr.lct XXADAT_INST_LOC_UPDATE.ldt ALR_ALERTS APPLICATION_SHORT_NAME='XXADAT' ALERT_NAME= 'XXADAT_INST_LOC_UPDATE'

PS:This ldt command brings all alerts in the instance in the ldt file, so u need to remove all except the one u want to move.


FNDLOAD apps/apps 0 Y UPLOAD $ALR_TOP/patch/115/import/alr.lct XXADAT_RESET_NR_TRACKING_NO_WHN_DEF.ldt

Workflow Upload
-----------------
WFLOAD apps/apps 0 Y UPLOAD XXCLOSNR.wft

In all these commands apps/apps is username/password of apps database user. Also, in all upload commands you can give full location from where your ldt/wft file should be picked to upload. I hope this helps.

Monday, December 7, 2009

Programatic PPR in OAF.

I was recently helping a friend in a customization in OAF, where through personalization, he wanted to put a ppr action in a seeded page in a seeded item. He was trying to do that customization using fire action, which was not acceptable by customer due to obvious reasons. Since, I think this section is missing in developers' guide, and is quite simple to approach, here is code you can write in process request of CO to attach programmatic PPR. lets take a simple example of attaching PPR to a message choice bean :

//In Process Request()
{
//Please attach PPR only to those UIX beans which support it
//otherwise it may not work
OAMessageChoiceBean mcb=(OAMessageChoiceBean)webBean.findChildRecursive("");
FireAction firePartialAction = new FirePartialAction("empPositionChange");
mcb.setAttributeValue(PRIMARY_CLIENT_ACTION_ATTR,firePartialAction);
}

//In process form request
if ("empPositionChange".equals(pageContext.getParameter(OAWebBeanConstants.EVENT_PARAM)))
{
//ur logic on PPR

//if PPR is attached in a table/hgrid child then we can find the row
//reference by
String rowReference =pageContext.getParameter(OAWebBeanConstants.EVENT_SOURCE_ROW_REFERENCE);
}

I hope this helps. Happy oaf extensions :)!

Monday, November 30, 2009

Setting Query Dynamically in View Object. (Why in some cases even after using setQuery(), VO picks default query.)

Couple of time while making view object in OAF pages, you have conditions where instead of where clause or order by clause, you need to change the entire source of sql query of View Object.
There can be n number of such scenarios, for example you are using a querybean in a search page, and in a certain condition, you have to change your query, your VO is attached to a LOV and in certain condition you need the query to change in order to optimize the VO sql query etc. Following points always keep in mind while using setQuery():

1) The new query you want to set in the view object should have same column name and types as the original VO query.

2)As per OAF coding standards all coding related to setting where clause, order by clause or setQuery should be written in ViewObjectImpl class, so in such case, you should generate ViewObjectImpl class.

3)Most Important :Always call vo.setFullSqlMode(FULLSQL_MODE_AUGMENTATION) before calling setquery() on the view object.This ensures that the order by or the WHERE clause, that is generated by OA Framework, can be correctly
appended to your VO. The behavior with FULLSQL_MODE_AUGMENTATION is as follows:

a) The new query that you have programmatically set takes effect when you call setQuery and execute the query.
b) If you call setWhereClause or if a customer personalizes the criteria for your query region, BC4J augments the whereClause on the programmatic query that you set.

For example:
select * from (your programmatic query set through setQuery)
where (your programmatic where clause set through setWhereClause)
order by (your programmatic order set through setOrderBy)
The same query is changed as follows if a customer adds a criteria using personalization:
select * from (your programmatic query set through setQuery)
where (your programmatic where clause set through setWhereClause) AND
(additional personalization where clause)
order by (your programmatic order set through setOrderBy)_

Warning: If you do not set FULLSQL_MODE_AUGMENTATION, the whereClause and/or the
orderBy, which was set programmatically, will not augment on the new query that you set using setQuery. It will instead augment on your design time VO query.


Due to timing issues, always use the controller on the query region/LOV region while using setQuery().

Friday, September 25, 2009

Dynamically changing multiselect to single select in Table in OAF

I would like to share a small piece of code, which recently a friend shared with me, this is a typical requirement, which might look too trivial at first shot, but is quite easy to implement. Suppose you have a table where in you have a requirement to have single or multi selection based on a dynamic conditon eg, some selection or press of a button you wanna change the multiselection to single selection and vice versa.

In such scenario, in the action of the event you redirect to same page and use following code in process request on the base of parameter you set in session/pagecontext :
// Hiding the Multiple Select Table Action and add single selection
OAAdvancedTableBean tabBean = (OAAdvancedTableBean)webBean.
findIndexedChildRecursive("[table item id]");

//Since the multiselect is at the end, get the total count -1 for removing
int count = tabBean.getIndexedChildCount(pageContext.getRenderingContext());
System.out.println("Child Count "+count);
tabBean.removeIndexedChild(count-1);

OAMultipleSelectionBean sel = (OAMultipleSelectionBean)tabBean.getTableSelection();
OASingleSelectionBean singleSelection = new OASingleSelectionBean() ;
singleSelection.setText("Select");

//Set the attribute for the single select
singleSelection.setViewAttributeName("SelectFlag");
tabBean.setTableSelection(singleSelection);

I hope this helps anybody who tries to implement such scenario!

Saturday, June 6, 2009

Error while running a OAF page with 12.1.1 (jdeveloper patch 8431482

We recetly migrated to newly released on Oracle apps R12.1.1.We tried using jdeveloper with patch 8431482 which is mentioned in metalink Doc Id 416708.1.We got following error stack:
Error Page
Exception Details.
oracle.apps.fnd.framework.OAException: Application: FND, Message Name: FND_GENERIC_MESSAGE. Tokens: MESSAGE = java.lang.NullPointerException;
at oracle.apps.fnd.framework.OAException.wrapperException(Unknown Source)
at oracle.apps.fnd.framework.CreateIcxSession.getEncryptedSessId(Unknown Source)
at oracle.apps.fnd.framework.CreateIcxSession.createSession(Unknown Source)
at _runregion._jspService(_runregion.java:132)
at com.orionserver.http.OrionHttpJspPage.service(OrionHttpJspPage.java:59)
at oracle.jsp.runtimev2.JspPageTable.service(JspPageTable.java:462)
at oracle.jsp.runtimev2.JspServlet.internalService(JspServlet.java:594)
at oracle.jsp.runtimev2.JspServlet.service(JspServlet.java:518)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:856)
at com.evermind.server.http.ServletRequestDispatcher.invoke(ServletRequestDispatcher.java:713)
at com.evermind.server.http.ServletRequestDispatcher.forwardInternal(ServletRequestDispatcher.java:370)
at com.evermind.server.http.HttpRequestHandler.doProcessRequest(HttpRequestHandler.java:871)
at com.evermind.server.http.HttpRequestHandler.processRequest(HttpRequestHandler.java:453)
at com.evermind.server.http.HttpRequestHandler.serveOneRequest(HttpRequestHandler.java:221)
at com.evermind.server.http.HttpRequestHandler.run(HttpRequestHandler.java:122)
at com.evermind.server.http.HttpRequestHandler.run(HttpRequestHandler.java:111)
at oracle.oc4j.network.ServerSocketReadHandler$SafeRunnable.run(ServerSocketReadHandler.java:260)
at oracle.oc4j.network.ServerSocketAcceptHandler.procClientSocket(ServerSocketAcceptHandler.java:239)
at oracle.oc4j.network.ServerSocketAcceptHandler.access$700(ServerSocketAcceptHandler.java:34)
at oracle.oc4j.network.ServerSocketAcceptHandler$AcceptHandlerHorse.run(ServerSocketAcceptHandler.java:880)
at com.evermind.util.ReleasableResourcePooledExecutor$MyWorker.run(ReleasableResourcePooledExecutor.java:298)
at java.lang.Thread.run(Thread.java:595)
## Detail 0 ##
java.lang.NullPointerException
at oracle.apps.fnd.security.SessionManager.recordSuccess(SessionManager.java:3820)
at oracle.apps.fnd.security.SessionManager.validateLogin(SessionManager.java:2082)
at oracle.apps.fnd.security.SessionManager.validateLogin(SessionManager.java:1946)
at oracle.apps.fnd.framework.CreateIcxSession.getEncryptedSessId(Unknown Source)
at oracle.apps.fnd.framework.CreateIcxSession.createSession(Unknown Source)
at _runregion._jspService(_runregion.java:132)
at com.orionserver.http.OrionHttpJspPage.service(OrionHttpJspPage.java:59)
at oracle.jsp.runtimev2.JspPageTable.service(JspPageTable.java:462)
at oracle.jsp.runtimev2.JspServlet.internalService(JspServlet.java:594)
at oracle.jsp.runtimev2.JspServlet.service(JspServlet.java:518)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:856)
at com.evermind.server.http.ServletRequestDispatcher.invoke(ServletRequestDispatcher.java:713)
at com.evermind.server.http.ServletRequestDispatcher.forwardInternal(ServletRequestDispatcher.java:370)
at com.evermind.server.http.HttpRequestHandler.doProcessRequest(HttpRequestHandler.java:871)
at com.evermind.server.http.HttpRequestHandler.processRequest(HttpRequestHandler.java:453)
at com.evermind.server.http.HttpRequestHandler.serveOneRequest(HttpRequestHandler.java:221)
at com.evermind.server.http.HttpRequestHandler.run(HttpRequestHandler.java:122)
at com.evermind.server.http.HttpRequestHandler.run(HttpRequestHandler.java:111)
at oracle.oc4j.network.ServerSocketReadHandler$SafeRunnable.run(ServerSocketReadHandler.java:260)
at oracle.oc4j.network.ServerSocketAcceptHandler.procClientSocket(ServerSocketAcceptHandler.java:239)
at oracle.oc4j.network.ServerSocketAcceptHandler.access$700(ServerSocketAcceptHandler.java:34)
at oracle.oc4j.network.ServerSocketAcceptHandler$AcceptHandlerHorse.run(ServerSocketAcceptHandler.java:880)
at com.evermind.util.ReleasableResourcePooledExecutor$MyWorker.run(ReleasableResourcePooledExecutor.java:298)
at java.lang.Thread.run(Thread.java:595)
java.lang.NullPointerException
at oracle.apps.fnd.security.SessionManager.recordSuccess(SessionManager.java:3820)
at oracle.apps.fnd.security.SessionManager.validateLogin(SessionManager.java:2082)
at oracle.apps.fnd.security.SessionManager.validateLogin(SessionManager.java:1946)
at oracle.apps.fnd.framework.CreateIcxSession.getEncryptedSessId(Unknown Source)
at oracle.apps.fnd.framework.CreateIcxSession.createSession(Unknown Source)
at _runregion._jspService(_runregion.java:132)
at com.orionserver.http.OrionHttpJspPage.service(OrionHttpJspPage.java:59)
at oracle.jsp.runtimev2.JspPageTable.service(JspPageTable.java:462)
at oracle.jsp.runtimev2.JspServlet.internalService(JspServlet.java:594)
at oracle.jsp.runtimev2.JspServlet.service(JspServlet.java:518)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:856)
at com.evermind.server.http.ServletRequestDispatcher.invoke(ServletRequestDispatcher.java:713)
at com.evermind.server.http.ServletRequestDispatcher.forwardInternal(ServletRequestDispatcher.java:370)
at com.evermind.server.http.HttpRequestHandler.doProcessRequest(HttpRequestHandler.java:871)
at com.evermind.server.http.HttpRequestHandler.processRequest(HttpRequestHandler.java:453)
at com.evermind.server.http.HttpRequestHandler.serveOneRequest(HttpRequestHandler.java:221)
at com.evermind.server.http.HttpRequestHandler.run(HttpRequestHandler.java:122)
at com.evermind.server.http.HttpRequestHandler.run(HttpRequestHandler.java:111)
at oracle.oc4j.network.ServerSocketReadHandler$SafeRunnable.run(ServerSocketReadHandler.java:260)
at oracle.oc4j.network.ServerSocketAcceptHandler.procClientSocket(ServerSocketAcceptHandler.java:239)
at oracle.oc4j.network.ServerSocketAcceptHandler.access$700(ServerSocketAcceptHandler.java:34)
at oracle.oc4j.network.ServerSocketAcceptHandler$AcceptHandlerHorse.run(ServerSocketAcceptHandler.java:880)
at com.evermind.util.ReleasableResourcePooledExecutor$MyWorker.run(ReleasableResourcePooledExecutor.java:298)
at java.lang.Thread.run(Thread.java:595)

We raised an SR with Oracle and they have responded back saying : Set profile "Sign-On:Notification" to "No" at site level, which resloves the issue and jdev works fine.
Hope this helps all, which are trying to use patch 8431482 for Apps 12.1.1.

Friday, June 5, 2009

Custom Logging in Java Layer in Oracle Applications R12

Hi All,
I have often felt for years simple SOP (System.out.Println) is the best way of debugging in java/j2ee applications. I have the evil habit.. of putting lots of SOPs while writing code for OAF or ADF or simple j2ee applications. I generally create a method with sop in it eg:

private void putLog(Object o)
{
System.out.println(o);
}

and call it with the code wherever required. The benefit is at the point of deployment of code, i just need to change this method from sop to writeDiagnostics().The worst part of diagnostic logging is when you enable logging on the Applications... "Show Log On Screen", it shows so many logs apart from my custom log statements, that its really difficult to debug.

The other standard way of debugging java code on server is using OACore log in OC4J server.In R12,
Goto $ORA_CONFIG_HOME/10.1.3/opmn/conf
take the backup of opmn.xml
edit opmn.xml for data id="java-options" and add the following:
-DAFLOG_ENABLED=true -DAFLOG_LEVEL=statement
-DAFLOG_MODULE=fnd%
-DAFLOG_FILENAME=/tmp/aflog.txt -Djbo.debugoutput=console

The log message should get written in,
$INST_TOP/logs/ora/10.1.3/opmn/oacore_default_group_1/oacorestd.out

But for this my code should contain SOPs, which is also a problem, because I don't wanna SOPs in my code after deployment on server, as SOPs have huge impact on performance.

So, I need a way (probably a magic .... :) )that my debug statements become SOPs when I run my code in jdeveloper automatically, and when I run the same code in server, these statements should go in a text file if I enable logging on server, lets' say we make it dependent on a profile , so that I can also disable these statements/logging in production just by changing profile value.

Ok so make this magic or logic happen I used a trick and have written a custom logger class with static methods, let see how this class works.Ok for making this logging happen follow these steps:

STEP1
-------------
Copy this class and put in any pkg under custom directory in java top, like I have used package xx.oracle.apps.ak.logging. Replace this pkg name with the pkg u want to put this class in.

package xx.oracle.apps.ak.logging;

import java.awt.Toolkit;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import oracle.apps.fnd.framework.OAException;
import oracle.apps.fnd.framework.server.OADBTransaction;
import oracle.apps.fnd.framework.webui.OAPageContext;
import java.io.PrintWriter;
import oracle.apps.fnd.common.WebAppsContext;
public class XxLogger
{
public XxLogger()
{
}

public static void putLog(OADBTransaction ot, Object o)
{
printLog(ot, null, null, null, o);
}

public static void putLog(OAPageContext pageContext, Object o)
{
printLog(null, null, pageContext, null, o);

}

public static void putLog(WebAppsContext ctx, Object o)
{
printLog(null, ctx, null, null, o);

}

public static void printLog(OADBTransaction ot, WebAppsContext ctx,
OAPageContext pageContext, Exception exp,
Object o)
{
FileOutputStream outFile = null;
try
{
//this line should throw headless exception on server
//due to opmn.xml configuration for oc4j node.
//and would work fine in local jdev
Toolkit.getDefaultToolkit().getScreenSize();
if (o == null)
{
exp.printStackTrace();
}
else
{
System.out.println(o);
}
}
catch (Exception e)
{
try
{
String profile = null;
if (ot != null)
{
profile = ot.getProfile("XX_LOG");
}
else if (pageContext != null)
{
profile = pageContext.getProfile("XX_LOG");
}
else if (ctx != null)
{
profile = ctx.getProfileStore().getProfile("XX_LOG");
}
if ((profile == null) || ("".equals(profile)))
{
//No logging

}
else
{
outFile = new FileOutputStream(profile + "/xx_log.log", true);
PrintStream ps = new PrintStream(outFile);

if (exp != null)
{
ps.append(new java.util.Date().toString() + " : " +
"*************************************Exception Details*****************************");
ps.append(" \n ");
exp.printStackTrace(ps);
ps.append("*************************************Exception Details End *****************************");

}
else
{
ps.append(new java.util.Date().toString() + " : " + o);
}
ps.append(" \n ");
}
}
catch (FileNotFoundException f)
{
// TODO
e.printStackTrace();
}
}
}


public static void printStack(OADBTransaction ot, Exception e)
{
printLog(ot, null, null, e, null);
}


public static void printStack(WebAppsContext ctx, Exception e)
{
printLog(null, ctx, null, e, null);
}


public static void printStack(OAPageContext pageContext, Exception e)
{
printLog(null, null, pageContext, e, null);
}

}

STEP2
----------
Now create a custom profile which will store path of log file we want to get generated,please note if this profile value is null then no log will be generated.Replace the profile name with "XX_LOG" in the code at all 3 places.

STEP3
========
set the profile value to the path where you wanna log file to be generated.Also please note you would have to manually create "xx_log.log" file there for the first time(for that just create a simple fie and rename as "xx_log.log"), this I have done purposely in code, so that even if by mistake profile value is not null in production, no log should be generated.

STEP 4
===========
Use this logging in OAF/JTT/JTF/JSP file :

In CO
========
XxLogger.putLog(OAPageContext pageContext, Object o)

In AM,VO,EO
============
XxLogger.putLog(OADBTransaction ot, Object o)


In JTT/JTF
============
import oracle.apps.fnd.common.WebRequestUtil;
import oracle.apps.fnd.common.WebAppsContext;
WebAppsContext ctx = WebRequestUtil.validateContext(request, response);
XxLogger.putLog(WebAppsContext ctx, Object o)

STEP 5
===========
For printing stacktrace of exceptions :


In CO
========
XxLogger.printStack(OAPageContext pageContext, Exception e)

In AM,VO,EO
============
XxLogger.printStack(OADBTransaction ot, Exception e)


In JTT/JTF
============
import oracle.apps.fnd.common.WebRequestUtil;
import oracle.apps.fnd.common.WebAppsContext;
WebAppsContext ctx = WebRequestUtil.validateContext(request, response);
XxLogger.printStack(WebAppsContext ctx, Exception e)


We are done the log would now be generated at the position marked in profile and the these messages will automatically converted to SOPs when you run your code in local jdev

Advantages of this logging
=============================
1) You don't need to change your code again and again while working in local or deployed on server.
2) If the development is done in a team, everybody in the team can set the profile at his/her user level so that different log is generated for each user.
3)This code when run in jdeveloper without any change all putLog() and printStack() statements will convert into System.out.println.
4) On server if profile value is set to null , no log is generated, else if some location is set, log is generated there.
5) Same class can be used in all OAF AND JSP components.
6) All methods putLog and printStack are static, so you need not instanciate the class, direct call of method will work.
7) Sometimes while working at offshore, you don't have access to DBA, who can enable log.... :) this is my personal one... u might not face it.

Logic of how code works
=========================
If you have read the code of class , in all the methods code essentially calls printLog method, which has the first line as

Toolkit.getDefaultToolkit().getScreenSize();
This line throws headless exception on server mainly because opmn.xml file configuration by default throws headless exception if any JAVA awt api is called, but this line works fine in jdev, so we utilise this logic to identify whether the code is running on server or jdev. Accordingly it writes the statements in a file, if file is enabled or else calls SOP in jdev.

Monday, April 6, 2009

MOAC(Multi Org Access Control) in OA Framework

Hi All,
After a long time a small and crisp article of how you can set MOAC in your custom OAF code. With release of R12 Oracle EBS has introduced a security feature in apps for multi -org access. Lets have a quick introduction of what is MOAC all abaout :

What is MOAC ?
---------------
The Access Control feature in Release 12 allows the user to enter or query records in one or more operating units without changing application responsibility. It is the system administrator’s discretion to either implement the feature or use the same multiple organizations profile option setting available before Release 12 by using the single operating unit mode (i.e. one operating unit for a responsibility).
In Release 12, the multiple organizations context value is no longer initialized by the FND_GLOBAL.APPS_INITIALIZE routine thereby reducing unnecessary context setting and resource consumption for applications that do not use operating unit context for data security.
To use the single operating unit mode, you must set the value for the "Initialization SQL Statement – Custom profile" to "mo_global.init('S',null);". This initializes the operating unit context based on the "MO: Operating Unit" profile option and the "MO: Security Profile" profile option must not be set.
Fresh install of Release 12 Application is enabled with multiple organizations, however, the system administrator must create operating units to use multi organizations sensitive application products. The user can create new operating units in the Accounting Setup Manager page in addition to HRMS’s Define Organizations page.


MOAC Implementation In Apps
-----------------------------
A new or fresh installation of an Oracle Applications instance does not automatically enable multiple organizations. Typically, the system administrator defines "MO: Operating Unit" profile at Responsibility and/or User level. The "organization_id" of the "MO: Operating Unit" profile option value filters the transactional data. The CLIENT_INFO application context space stores the multiple organizations context value.
Multi-Org views use the following WHERE clause to filter application records:
'org_id = substrb(userenv(''CLIENT_INFO''),1,10)'

MOAC in terms of OAF:
----------------------
Its very natural while developing extension or developing OAF custom pages , you may require quering of views, synonyms which use MOAC via VO.Also, it is possible that you might be calling some standard Oracle PL/SQL APIs which usually need MOAC context to be set.

Every transaction that requires multiple organizations must call the Multiple Organizations initialization in the root Application Module (AM).
Use the following declarative mechanism to initialize the multiple organizations settings for application teams to implement multiple organizations:
1. To enable multiple organizations for the root application module , go to the BC4J Application Module wizard - Properties section and specify the property as MULTIORG_ENABLED and value as either S (single operating unit mode) or M (Multiple operating unit mode).
2. Click Add, then Apply or OK.
On specifying this property, the OA Framework automatically initializes the multiple organizations context at the following appropriate program event points:
1. When reserving or activating the application module.
2. When initializing or validating the Oracle Applications user session.
You initialize the context once for each transaction and session and not instantiate for every page. If your transaction retains the root AM, then the above steps are the easiest to initialize multiple organizations.

If a transaction has multiple pages and the root AM is not retained, then you must call the method OADBTransaction.setMultiOrgAccess to initialize the multiple organizations context to help the user select an operating unit for a transaction.Here is how u can code in AM
OADBTransactionImpl trx = (OADBTransactionImpl)getOADBTransaction(); getOADBTransaction().setMultiOrgAccess(String.valueOf(trx.getOrgId()),String.valueOf(trx.getSecurityProfileId()),trx.getApplicationShortName());



If the operating unit the user selected must appear in the subsequent pages, then pass the curr_org_id to the page and use OADBTransaction.setMultiOrgPolicyContext method to set the operating unit context for the pages that need multiple organizations.
OADBTransactionImpl trx = (OADBTransactionImpl)getOADBTransaction();
getOADBTransaction().setMultiOrgPolicyContext("S",trx.getMultiOrgCurrentOrgId());

There is often a case when you create a custom application in apps under $JAVA_TOP, in order to keep all your customizations, lets say XXABC.When we make a new application in Apps like XXABC, we need to register the application for Multi-Org as single or multiple.
This is important, if we are defining new custom responsibilities on this application and we are planning to have custom, as well as seeded pages attached in this responsibility.If you have this scenario, where you custom responsibility is defined on custom application and it is using seeded pages as well as custom pages, you may face a error in your multi-org enabled seeded AM pages like :
oracle.apps.fnd.framework.OAException: Application: FND, Message Name: FND_GENERIC_MESSAGE. Tokens: MESSAGE = java.sql.SQLException: ORA-20001: SQL_PLSQL_ERROR: N, ROUTINE, MO_GLOBAL.INIT, N, ERRNO, -20001, N, REASON, ORA-20001: SQL_PLSQL_ERROR: N, ROUTINE, MO_GLOBAL.SET_ORG_ACCESS, N, ERRNO, -20001, N, REASON, ORA-20001: APP-FND-02938: Multi-organization routine failed to initialize a session for the product: &PRODUCT. Please inform your support representative.
ORA-06512: at "APPS.FND_MESSAGE", line 509
ORA-06512: at "APPS.MO_GLOBAL", line 36
ORA-06512: at "APPS.MO_GLOBAL", line 757
ORA-06512: at "APPS.MO_GLOBAL", line 700
ORA-06512: at line 1


The reason for this error is Oracle Apps seeded pages which have AM with multi-org enabled,if you will check the AM xml file , they use MULTIORG_ENABLED as Y and not as S or M . This is because in Apps, you can directly register an application with multi-org enabled in table fnd_mo_product_init by using API :
-- To enable MO access in a custom application:
begin
FND_MO_PRODUCT_INIT_PKG.register_application('XXABC','SEED','N');
end;

Since, seeded applications are already registered here, its not a problem, when you run seeded pages because MULTIORG_ENABLED=Y in AM sets correct multi org access, but in case of custom application/responsibility based on custom application running seeded pages throws error, because the custom application XXABC is not registered in table fnd_mo_product_init. Hence , in order to run both seeded pages and custom pages fine i.e. code work correctly in case of MULTIORG_ENABLED=Y (used by seeded pages) or MULTIORG_ENABLED=S/M (custom pages), register the custom application using the FND_MO_PRODUCT_INIT_PKG.register_application API.

Wednesday, January 7, 2009

ADF 10g : How to show faces message both globally and below the UI component.

Hi all,
Welcome again, after a long time I am finally back on blogging.. in the time of economy recession!I think I was not able to update my blog from almost last 7-8 months because of various resons, joining new company, working new technologies of Oracle Fusion and working on multiple projects simultaneously.

In the past 6 months i have worked as an architect on two ADF 10G projects. When I compare ADF with my OA Framework experience, I find ADF to be much more challenging as well as flexible to mold as per the requirement in your project.

From now I would be coming up with small articles on ADF which will be helpful, to develop useful APIs by product architects, so that other developers would be able to use them , without worrying about the actual implementation.

One task in ADF which looks very easy, is showing of different jsf messages.One great feature of ADF is that Faces messages can be shown globally at the top of page, as well as just below the UI ADF bean, which is very illustrative for users.

CASE I:Showing Global Message in an ADF page
-----------------------------------------------
1)af:messages tag is used for showing global messages in an ADF page.This tag is created automatically whenever you drop an input widget from the
Data Control Palette. However, if you need to insert the tag, simply insert the
following code within the afh:body tag:


2. In the Property Inspector set the following attributes:
■ globalOnly: By default ADF Faces displays global messages (i.e., messages
that are not associated with components) followed by individual component
messages. If you wish to display only global messages in the box, set this
attribute to true. Component messages will continue to display with the
associated component.
■ message: The main message text that displays just below the message box
title, above the list of individual messages.
■ text: The text that overrides the default title of the message box.

Code for displaying global Faces message :
//You need to enter error description
String error_decp="";
//You need to enter error detail
String error_detail="";
//Get current instance of faces context
FacesContext fc = FacesContext.getCurrentInstance();
//Creating faces message with description and detail
FacesMessage message = new FacesMessage(error_descp, error_detail);
//setting severity of message,i.e. message type
message.setSeverity(FacesMessage.SEVERITY_ERROR);
//Adding message to faces context.
fc.addMessage(null, message);

CASE II:Showing Faces Message in an ADF UI COMPONENT along with global error message:
-----------------------------------------------------------------------------

a)JSF Validator :
-------------------
This approcah is typically used when you have same kind of validation repeatdly in your code. Lets take a very generic example you need to verify that the entered number should have precision of 2 decimal places, i.e. whatever number user enters should have 2 digits after decimal.You can write a jsf validator for this register in the application and use in any othe pages. If you are new to JSF validator just search google you will find numerous examples of how to write a JSF validator.



b) Directly adding message with UI component based of custom code in backing bean:
--------------------------------------------------------------------------------

i)In complex data UI components like table:
--------------------------------------------
String global_error=""; //Error that need to be shown at top of the page
String component_error="";//Error that need to be shown on the component
String formId="";// Id of Form bean
String TableId="";//Id of table bean
String rowIndex="";//String value of row index
String message="";//message type

FacesContext fc = FacesContext.getCurrentInstance();
FacesMessage message = new FacesMessage(global_error, component_error);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
fc.addMessage(formId+":"+TableId+":"+String.valueOf(rowIndex)+":"+ComponentId, message);


ii)In simple data UI components like input text:
---------------------------------------------
FacesContext fc = FacesContext.getCurrentInstance();
FacesMessage message = new FacesMessage(global_error, component_error);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
fc.addMessage(formId+":"+ComponentId, message);

I hope the above article has provided a brief description about how to show global and UI specific messages , you can use in ADF via JSF APIs.