Apr 23, 2012

This is a short post about how one can use ADF security to hide/show links to the taskflows depending upon the user role. We can either use userInRole or taskflowViewable EL expressions on the visible or rendered property of the af:command link. But, choosing one over the other has repercussions on maintainability and also causes replication of security configuration in the source code   The preferred way, i think is to use taskflowViewable expression as it avoids the aforementioned problems. This can be seen by the sample usage of the aforementioned expressions below.
1. taskflowViewable :-
<af:commandLink id="cl1" rendered="#{securityContext.taskflowViewable['/pathtotaskflowdefinition/#taskflow_defn_id']}" >
  <af:setPropertyListener type="action"   from="/pathtotaskflowdefinition/#taskflow_defn_id" to="#{viewScope.NavBacking.taskFlowId}" />
              </af:commandLink>

2. isUserRole:-
<af:commandLink id="cl1" rendered="#{securityContext.userInRole['abcRole','xyzRole']}" >
   <af:setPropertyListener type="action"   from="/pathtotaskflowdefinition/#taskflow_defn_id" to="#{viewScope.NavBacking.taskFlowId}" />
              </af:commandLink>
 

As you can see the first expression is only concerned with implicitly restricting access to the users of the particular taskflow that have not been granted access in jazn-data.xml file whereas, the second expression is doing so explicitly by mentioning two specific application roles hence it is causing replication and maintenance issues.

The second snippet by explicitly specifying which users can access the taskflow is replicating the security configuration made in the jazn-data.xml and also if a need arises to grant or revoke access to the taskflow, code change will be required in the second scenario along with the merging of security policy where as in former case only thing required will be the merging of security policy. Hence you should prefer taskflowViewable over userInRole.

Posted on Monday, April 23, 2012 by Unknown

Apr 4, 2012

There is a caveat i wanted to highlight with respect to view criteria that has a performance impact.
if you use a view criteria it is applied after the query block as shown below.
This example is based on the scott schema.
select * from (select empno from emp)QRSLT where empno=2;
Now the query execution plan will be the same to the output of the following query where you filter the records inside rather than using a viewcriteria.
select empno from emp where empno=:bvar;
The plan is shown below :-

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 56244932

----------------------------------------------------------------------------
| Id  | Operation         | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |        |     1 |     4 |     0   (0)| 00:00:01 |
|*  1 |  INDEX UNIQUE SCAN| PK_EMP |     1 |     4 |     0   (0)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("EMPNO"=TO_NUMBER(:BVAR))

But the fact that ADF view criteria where clause is applied after the main query block will have a huge performance impact in case you end up using a analytical function inside your query block as shown below.

select * from (select empno,row_number() over (order by 1) from emp) QRSLT where empno=2;

The query plan is shown below
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1674134785

----------------------------------------------------------------------------
| Id  | Operation         | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |        |    14 |   364 |     1   (0)| 00:00:01 |
|*  1 |  VIEW             |        |    14 |   364 |     1   (0)| 00:00:01 |
|   2 |   WINDOW NOSORT   |        |    14 |    56 |     1   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN| PK_EMP |    14 |    56 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("Q1"."EMPNO"=2)

Without view criteria

The query is shown below :-
select empno,row_number() over (order by 1) from emp where empno=:bvar

The execution plan :-
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------
Plan hash value: 1807209526

-----------------------------------------------------------------------------
| Id  | Operation          | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |        |     1 |     4 |     0   (0)| 00:00:01 |
|   1 |  WINDOW NOSORT     |        |     1 |     4 |     0   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN| PK_EMP |     1 |     4 |     0   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("EMPNO"=TO_NUMBER(:BVAR))

Now this plan is far better as it will firstly filter the rows and then the analytical function row_number() will be applied. You can see that in the plan output that the rows that need to be accessed are 14 (all the rows) and a full index scan (all the index blocks are going to be visited) is going to take place.

The plan output differs due to the fact that whenever you are using analytical function or a rownum clause inside your query. The query optimizer will not push predicates to the inner query block because it can effect the result set returned by the query. While in the former case the query optimizer will push the predicates inside hence execution plans are same.

Hence if you are using analytical functions or a rownum clause in your query be aware of the performance impact and always filter the rows inside.

Note:Also don't choose to ignore null values for predicates that are required and create proper indexes on the table structure depending upon how you filter results. For ex: If predicate is upper(ename)=upper(:bvar) then create a index on upper(ename) .

Posted on Wednesday, April 04, 2012 by Unknown

Mar 7, 2012

In this post i would discuss a simple workaround to the fact that ADF 11g does not support a explicit window close event. The workaround is to use a library like dojo or jquery. One should use these libraries as it will make the function cross browser compatible.

The code mentioned below logs the user out when they press the close button on browser. It essentially calls the unLoad function which makes the use of dojo.io.script.get function (this dojo function also allows cross site data access using callbacks).
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/dojo/1.4/dojo/dojo.xd.js" ></script>
<script type="text/javascript">
dojo.require("dojo.io.script");
var unLoad = function() {
    dojo.io.script.get({
    url:'http://127.0.0.1:7101/myapp/adfAuthentication?logout=true',
    timeout:15000,
    });
 
 
  }
  dojo.addOnWindowUnload(unLoad);
</script>

Add this code in the fragment or the jspx page. Also note that if you are adding it in a fragment make sure to add the script in a af:panelFormLayout.

Note:
The documentation states the following.
However, you need to be careful about what work you do during dojo.addOnUnload/window.onbeforeunload since 
this event can be triggered if the user 
clicks a link to download a file or a javascript: link. 
In these cases, the document will not be destroyed 
and so any destructive operations done 
during a dojo.addOnUnload callback may be premature.

Therefore you will have to ignore such events by using something like this.
<script type="text/javascript">
dojo.require("dojo.io.script");
var doUnload=true;
var unLoad = function() {
if(doUnload){
    dojo.io.script.get({
    url:'http://127.0.0.1:7101/myapp/adfAuthentication?logout=true',
    timeout:15000,
    });
  }
doUnload=true;
  };
var init=function(){
dojo.connect(dojo.doc, "onkeypress", function(e){
   switch(e.charOrCode){
        case dojo.keys.F5: doUnload=false;
        break; 
        case dojo.keys.BACKSPACE: doUnload=false;
        }
 dojo.stopEvent(e); 
});
dojo.connect(dojo.doc, "onclick", function(e){
   doUnload=false;
});

};
dojo.addOnWindowUnload(unLoad);
dojo.addOnLoad(function (){init();});
</script>


Posted on Wednesday, March 07, 2012 by Unknown

Feb 22, 2012

This is a simple procedure i created for unlocking user account and resetting the password to the original password, Although a very small utility it helps you with unlocking accounts that you tend to use after a long time which in my case were the SOA server and the BAM server user accounts.
CREATE OR REPLACE PROCEDURE "SYS"."ACCOUNT_UNLOCKER" ( p_user in varchar2 )
is
temp_password varchar2(255);
begin
select password into temp_password from sys.user$ where upper(name)=upper(p_user
);
execute immediate 'alter user '||p_user||' account unlock ' ;
execute immediate 'alter user '||p_user|| ' identified by values '''||temp_passw
ord||'''';
end;




Posted on Wednesday, February 22, 2012 by Unknown

Jan 1, 2012

Following is a technical white paper that shows you how to reset field values in ADF using ADF JavaScript client library.

View/Download

Posted on Sunday, January 01, 2012 by Unknown

Nov 24, 2011

To override the default error handler one can extend the  DCErrorHandlerImpl. In this you can process the exceptions or can choose to suppress them. In this post i will explain a way to suppress the RowValException message and display only the detail entity attribute level exception ie AttrValException.

In ADF the exceptions are grouped and raised together as a single object. For example let's say that 2 of the attributes fail the mandatory validation then the exceptions raised for them will be 2 individual AttrValException these are then attached as detail objects into a RowValException and if there are many such RowValException they will be attached and raised together as a TxnValException.

So now to handle the aforementioned task we need to recursively  process the exceptions and suppress the RowValException message. The snippet below shows that :-

public class MyErrorHandler extends DCErrorHandlerImpl {
    private static final ADFLogger logger = ADFLogger.createADFLogger(VleAdminErrorHandler.class);
    private static ResourceBundle rb = ResourceBundle.getBundle("mypackage.myBundle");
    public MyErrorHandler() {
        super(true);
    }

    public MyErrorHandler(boolean setToThrow) {
        super(setToThrow);
    }

    public void reportException(DCBindingContainer bc, java.lang.Exception ex) {
        disableAppendCodes(ex);
        logger.info("entering reportException() method");
        BindingContext ctx = bc.getBindingContext();
        String err_code;
        err_code = null;
        if(ex instanceof NullPointerException){
              logger.severe(ex); 
              JboException e = new JboException(rb.getString("STANDARD_ERROR_MESSAGE"));
              super.reportException(bc, e);
            }
        else if(ex instanceof RowValException){
          
          Object[] exceptions= ((RowValException)ex).getDetails();
          if(exceptions!=null){
            for(int i=0;i<exceptions.length;i++){
                  if(exceptions[i] instanceof RowValException){
                       this.reportException(bc, (Exception)exceptions[i]);                                 
                  }
                  else if(exceptions[i] instanceof AttrValException){
                super.reportException(bc, (Exception)exceptions[i]);
                  }
              }
          }
            else{
            this.reportException(bc, ex);
            }
            
        }
       else if (ex instanceof TxnValException) {
          Object[] exceptions= ((TxnValException)ex).getDetails();
          if(exceptions!=null){
          for(int i=0;i<exceptions.length;i++){
              if(exceptions[i] instanceof RowValException){
                   this.reportException(bc, (Exception)exceptions[i]);                                 
              }
              else{
            super.reportException(bc, (Exception)exceptions[i]);
              }
          }
          }
          else {
                super.reportException(bc, ex);
              }
        }
      
       else if (ex instanceof oracle.jbo.DMLException) {
           JboException e = new JboException(rb.getString("STANDARD_ERROR_MESSAGE"));
            super.reportException(bc, e);
        }
       else if(ex instanceof javax.xml.ws.WebServiceException){
            JboException e=new JboException(rb.getString("WEB_SERVICE_EXCEPTION"));
            super.reportException(bc, e);
            }
       
        else if (ex instanceof JboException) {
            super.reportException(bc, ex);
        }

    }


    public static FacesMessage getMessageFromBundle(String key, FacesMessage.Severity severity) {
        ResourceBundle bundle = ResourceBundle.getBundle("sahaj.apps.vleadministration.view.resources.VLEAdministrationUIBundle");
        String summary = JSFUtil.getStringSafely(bundle, key, null);
        String detail = JSFUtil.getStringSafely(bundle, key + "_detail", summary);
        FacesMessage message = new FacesMessage(summary, detail);
        message.setSeverity(severity);
        return message;
    }
  private void disableAppendCodes(Exception ex) {
      if (ex instanceof JboException) {
        JboException jboEx = (JboException) ex;
        jboEx.setAppendCodes(false);
        Object[] detailExceptions = jboEx.getDetails();
        if ((detailExceptions != null) && (detailExceptions.length > 0)) {
          for (int z = 0, numEx = detailExceptions.length; z < numEx; z++) {
            disableAppendCodes((Exception) detailExceptions[z]);
          }
        }
      }
  }
  @Override  
    protected boolean skipException(Exception ex) {  
      if (ex instanceof JboException) {  
        return false;  
      } else if (ex instanceof SQLIntegrityConstraintViolationException) {  
        return true;  
      }  
      return super.skipException(ex);  
    }

    @Override
    public String getDisplayMessage(BindingContext bindingContext,
                                    Exception exception) {
        return super.getDisplayMessage(bindingContext, exception);
    }

    @Override
    public DCErrorMessage getDetailedDisplayMessage(BindingContext bindingContext,
                                                    RegionBinding regionBinding,
                                                    Exception exception) {
        
        
    return super.getDetailedDisplayMessage(bindingContext, regionBinding, exception);
    }
} 

In this snippet i am recursively processing TxnValException->RowValException -> AttrValException. The AttrValException is being passed in the super call to the DCErrorHandlerImpl to process and display the message.




Posted on Thursday, November 24, 2011 by Unknown

Nov 17, 2011

I had a recently asked a question on bulk uploads in OTN forum. Seems the performance that i would receive for bulk update/insert scenarios isn't at par.
So i created the procedure and a custom object type and table of that object type which provides far better performance.

Let's say your table structure is as follows :-

--     TXN_TBL
("TXN_ID" Number,
"USER_NAME" VARCHAR2(50 BYTE),
"TXN_DATE" DATE,
"TXN_AMOUNT" NUMBER)

So you can basically create a object type mirroring that structure as follows
create or replace type TXN_TBL_R is object
("TXN_ID" Number,
"USER_NAME" VARCHAR2(50 BYTE),
"TXN_DATE" DATE,
"TXN_AMOUNT" NUMBER)
and then create a table type that will store record of these types:
create or replace type TXN_TBL_TB as table of  TXN_TBL_R
The procedure that will perform the updates and or inserts is shown in the below snippet:-

create or replace procedure B_INSERT(p_in IN TXN_TBL_TB) as
cursor for_insert is select * from TABLE(p_in) rt where not exists(select tmp.TXN_ID from TXN_TBL tmp where rt.TXN_ID=tmp.TXN_ID);
cursor for_update is select * from TABLE(p_in) rt where exists(select tmp.TXN_ID from TXN_TBL tmp where rt.TXN_ID=tmp.TXN_ID);
temp_insert TXN_TBL_R;
temp_update TXN_TBL_R;
begin
for temp_update in for_update loop
update TXN_TBL tmp set tmp.USER_NAME= temp_update.USER_NAME,tmp.TXN_DATE=temp_update.TXN_DATE,tmp.TXN_AMOUNT=temp_update.TXN_AMOUNT where tmp.TXN_ID= temp_update.TXN_ID;
end loop;
for temp_insert in for_insert loop
insert into TXN_TBL values(temp_insert.TXN_ID, temp_insert.USER_NAME, temp_insert.TXN_DATE, temp_insert.TXN_AMOUNT);
end loop;
end;

Then you can basically call this program from the ADF application using struct type. The snippet is shown below:-
/**
*@param valueSet the set of bean values
*/
 public void someMethod(Set valueSet){
         Connection conn=null;
        try {
          conn = getDBTransaction().createStatement(1).getConnection();
          StructDescriptor tblRecordStructType =
                          StructDescriptor.createDescriptor("TXN_TBL_R", conn);
        Iterator it= valueSet.iterator();
        Object txnArray[]=new Object[set.size()];
          while(it.hasNext()){
       SomeCustomBean detail=it.next();
  STRUCT tempStruct=new STRUCT(tblRecordStructType,conn,new Object[]{detail.getTranxId(),detail.getUserName(),detail.getTxnDate(),detail.getTransAmount()});  
    txnArray[i]=tempStruct;
    i=i+1;
    }
    //create array structure descriptor
     ArrayDescriptor txnTableDesc=ArrayDescriptor.createDescriptor("TXN_TBL",conn);
  //create an Array type with given structure definition
  ARRAY txnTableArray=new ARRAY(txnTableDesc,conn,txnArray); 
           String callableProcedureStatement=" begin  TMP_INSERT(?); end;" ;
          OracleCallableStatement st=null;
          st=(OracleCallableStatement)getDBTransaction().createCallableStatement(callableProcedureStatement, 0);  
          st.setARRAY(1, txnTableArray);
          st.executeUpdate();  
    this.getDBtransaction().commit();
        } catch (JboException e) {
this.getDBtransaction().rollBack();
         throw new JboException(e.getMessage());
   
        }
  }


This is basically it. This will perform faster than normal ADF update/insert.


Posted on Thursday, November 17, 2011 by Unknown

If you are using ADF11g version 11.1.1.3 and want to reset the values in the popup, you are not provided with the resetEditableValues option. But you can accomplish this using a custom javascript that i have made.

All you have to do is set clientComponent="true" for all the components that you want to reset and pass the client id of the root component to use this.

resetAction=function(clientId){
    var component= AdfPage.PAGE.findComponentByAbsoluteId(clientId);
    console.log(component);
    var components=component.getDescendantComponents();
    this.resetFunction(components);
    }
   resetFunction=function(components){
     for (var i=0;i<components.length;i++){
     if(components[i] instanceof AdfRichInputText){
     if(components[i].getVisible()&&!components[i].getDisabled()&&!components[i].getReadOnly()){
     components[i].setValue("");
     }
     }
     if(components[i] instanceof AdfRichSelectOneChoice){
    if(components[i].getVisible()&&!components[i].getDisabled()&&!components[i].getReadOnly()){
    components[i].resetValue();
    }
     }
     }
  }
   
    
Save the contents of this file to a JS file and then import it in your fragment or a jsf page.

Now you have to bind the parent container like panelFormLayout and then on action of the command button use ExtendedRenderKitService to pass the client id of the root component dynamically to resetAction Method so that this script always works. The snippet is shown below.

/**
     * Resets the popup
     * @return null
     */
    public String resetPopupAction() {
      FacesContext context = FacesContext.getCurrentInstance();
      ExtendedRenderKitService erks =
      Service.getRenderKitService(context, ExtendedRenderKitService.class);
      erks.addScript(context,"resetAction('"+formBinding.getClientId(context) +"')"); 
    
      return null;
    }


Hope this will be helpful :D

Posted on Thursday, November 17, 2011 by Unknown