nedelja, 16. december 2012

ADF BC: Accessing methods from AAAViewImpl from BBBViewRowImpl

We have two View Objects which are not connected with ViewLink. Let say they are EmployeesView and JobsView from HR schema. In database these two tables are connected with FK, but we don't use them in this example.

Our goal is to access attributes of JobView from EmployeesViewRowImpl class. Let say for some sort of calculation or setting of transient attribute - for example setting the JobTitle transient attribute added to EmployeesView.

This approach is different from my previous post  (http://sasocelarcadf.blogspot.com/2012/12/adf-bc-view-objects-attributes-from.html) because here we don't use View Link.



1. We create transient attribute named JobTitle in EmployeesView







2. We create EmployeesViewRowImpl class, and inside is getJobTitle() method which is the source for JobTitle transient attribute.



3. We create JobsViewImpl class and add the method named getJobTitle which returns job title for given job ID:



public String getJobTitle(String jobId){
    this.getRowSet().first();
    Row rw = null;                                              
    rw = this.getRow(new Key(new Object[]{jobId}));
    return (String) rw.getAttribute("JobTitle");
}


Tip: If we want to use this method as Groovy expression for example for defaulting the JobTitle transient attribute in EmployeesView we can use:

adf.source.getApplicationModule().findViewObject("JobsView1").getJobTitle(JobId);

In this case the Default value type of transient attribute must be Expression.

4. Go back to EmployeesViewRowImpl class and adapt getJobTitle() method. With vo.getJobTitle(getJobId() we call the upper method.

public String getJobTitle() {
    ApplicationModule vAppModule = this.getApplicationModule();
    JobsViewImpl vo = (JobsViewImpl)vAppModule.findViewObject("JobsView1");
    return vo.getJobTitle(getJobId());
}

5. Using Model tester we can see the result:


6. If we don't need method in JobsViewImpl, we can perform everything inside EmployeesViewRowImpl:

public String getJobTitle() {
    ApplicationModule vAppModule = this.getApplicationModule();
    ViewObject vo = vAppModule.findViewObject("JobsView1");
    RowSet rs = vo.getRowSet();
    Row rw = null;                                              
    rw = rs.getRow(new Key(new Object[]{getJobId()}));
    return (String) rw.getAttribute("JobTitle");        
}



ADF BC View Objects: Attributes from master table in detail table

Problem description

Let take typical scenario (HR schema):
We have DEPARTMENTS table which is master and EMPLOYEES table which is detail.
Often we want to show all EMPLOYEES (regardless of DEPARTMENTS ) and for each employee also DEPARTMENT_NAME which belongs to DEPARTMENTS table.



In Oracle Forms, we would typically create POST-QUERY-TRIGGER on block level where we perform SQL to get DEPARTMENT_NAME.

How to perform similar behaviour in ADF


Groovy approach



1. Suppose you created EOs, VOs for DEPARTMENTS and EMPLOYEES tables, created Associatons and View Links for EMP_DEPT_FK foreign key
2. Arrange Data Model in Application Module as follows:




3. In EmpDeptFkLink create Source Accessor named DepartmentsView1. This perform the "backward" connection to the master record of DEPARTMENTS from EMPLOYEES record.



4. Open EmployessView VO, go to Attributes and add new attribute

5. Name it DepartmentName, make it Transient, let Default Value to be an Expression type and actual value should be DepartmentsView1.DepartmentName. This is Groovy expression.



6. And now we can test Application Module:





 7. So far, so good. But when we meet the record with DEPARTMENT_ID = NULL we get this:


8.What now? With Groovy we can perform ifs, test for nulls,...
Let use some if DepartmentsView1.DepartmentName is not null then use DepartmentsView1.DepartmentName, otherwise use string "Undefined":



(DepartmentsView1.DepartmentName != null ? DepartmentsView1.DepartmentName:"Undefined")

Doesn't help. The right expression is :


(DepartmentsView1 != null ? DepartmentsView1.DepartmentName:"Undefined")

I suppose this is because Groovy first evaluates DepartmentView1 object and then DepartmentView1.DepartmentName object and the exception is thrown already when  DepartmentView1 is evaluated.



First Alternative approach 

We can achieve the same effect programaticaly.

1. Go back to EmployessView VO, go to Attributes, let Default Value to be an Literal type and empty actual default value.



2. Generate View Row Java Classes for EmployessView  and DepartmentsView. This action creates EmployeesViewRowImpl.java and DepartmentsViewRowImpl.java.




3. In EmployeesViewRowImpl find getDepartmentName() method and change it:




    public String getDepartmentName() {        
        DepartmentsViewRowImpl rowDept = (DepartmentsViewRowImpl)this.getDepartmentsView1(); 
        if (rowDept != null) return rowDept.getDepartmentName(); else return "Undefined";
    }




4.Test again Application Module and everything work fine. As we can see in code, we test the rowDept row object (not the attribute object) at for null same as in Groovy.


Second Alternative approach 

For second alternative approach we remove added Transient attribute DepartmentName on EmployessView and remove EmployeesViewRowImpl.java  and DepartmentsViewRowImpl.java if we continue with this approach after original and first alternative approach.

1.Open EmployessView and go to Entity Objects tab and throw Departments from available to selected. Change Join Type into "left outer join", so also employees not in any department are returned. That way the VO EmployessView is based on SQL joining two tables.



2.Open Attributes tab an add new attribute from entity:

3. Throw DepartmentName from Departmets from available to selected side.  On the selected side two attributes are created DepartmentName and DepartmentId1 (why exactly?).


4. And we can test the Appliacation module and it works for Employees with and without defined DEPARTMENT_ID:



Components on JSF Page

All three approaches has same (similar) effect on JSF page. When you enter value for DepartmentId and press Tab/Enter, the value for DepartmentName is evaluated. On DepartmentId af:inputText you set autoSubmit="true", on DepartmentName you set partialTriggers="it10".


<af:inputText value="#{bindings.DepartmentId.inputValue}" label="#{bindings.DepartmentId.hints.label}"
              required="#{bindings.DepartmentId.hints.mandatory}"
              columns="#{bindings.DepartmentId.hints.displayWidth}"
              maximumLength="#{bindings.DepartmentId.hints.precision}"
              shortDesc="#{bindings.DepartmentId.hints.tooltip}" id="it10" autoSubmit="true">
    <f:validator binding="#{bindings.DepartmentId.validator}"/>
    <af:convertNumber groupingUsed="false" pattern="#{bindings.DepartmentId.format}"/>
</af:inputText>
<af:inputText value="#{bindings.DepartmentName.inputValue}"
              label="#{bindings.DepartmentName.hints.label}"
              required="#{bindings.DepartmentName.hints.mandatory}"
              columns="#{bindings.DepartmentName.hints.displayWidth}"
              maximumLength="#{bindings.DepartmentName.hints.precision}"
              shortDesc="#{bindings.DepartmentName.hints.tooltip}" id="it11" partialTriggers="it10">
    <f:validator binding="#{bindings.DepartmentName.validator}"/>
</af:inputText>



Links and sources: