Showing posts with label ADF. Show all posts
Showing posts with label ADF. Show all posts

Wednesday, 21 October 2015

Get the size of a collection in EL using JSTL

Although the java.util.Collection interface defines a size method, it does not conform to the JavaBeans component design pattern for properties and so cannot be accessed by using the JSP expression language.

The JSTL length function can be applied to any collection supported by the c:forEach and returns the length of the collection.

When applied to a String, it returns the number of characters in the string.

Add the JSTL functions namespace to the page/fragment:

xmlns:fn="http://java.sun.com/jsp/jstl/functions"

then you can use the length expression:

rendered="#{fn:length(bindings.actuatorList.allRowsInRange) > 0}">

Other useful JSTL functions:

  • toUpperCase, toLowerCase: Changes the capitalization of a string 
  • substring, substringBefore, substringAfter: Gets a subset of a string 
  • trim: Trims white space from a string 
  • replace: Replaces characters in a string 
  • indexOf, startsWith, endsWith, contains, containsIgnoreCase: Checks whether a string contains another string 
  • split: Splits a string into an array 
  • join: Joins a collection into a string 
  • escapeXml: Escapes XML characters in a string

Using parametric Resource Bundle keys in EL expressions

Sometimes we have the need of defining parametric keys in a resource bundle file, to make the EL expressions in our ADF pages/fragments more readable and maintainable.

For this purpose, ADF provides EL functions which support both positional and named parameters.

  • af:formatString : for formatting a string with a positional parameter. 
  • af:formatString2 : for formatting a string with two positional parameters.
  • af:formatString3 : for formatting a string with three positional parameters.
  • af:formatString4 : for formatting a string with four positional parameters. 
  • af:formatNamed : for formatting a string with a named parameter. 
  • af:formatNamed2 : formatting a string with two named parameters. 
  • af:formatNamed3 : for formatting a string with three named parameters. 
  • af:formatNamed4 : for formatting a string with four named parameters. 

If we use named parameters in our resource bundles:

SCAN_DURATION_VALUE={HOURS} hours, {MINS} minutes 

We can use the formatNamed functions:

<af:outputText value="#{af:formatNamed2(myBundle.SCAN_DURATION_VALUE,
                                                 'HOURS', varHours, 
                                                 'MINS', varMins)}"/>

If we use positional parameters instead:
 
TASK_CURRENT_STEP=Currently executing step {0} of {1}

Then we can use the formatString functions:

<af:outputText value="#{af:format2(myBundle.TASK_CURRENT_STEP, var1, var2)}"/>

In alternative, we can use the JSF outputformat component, which uses a positional approach:
  
<h:outputFormat value="#{myBundle.TASK_CURRENT_STEP}" id="of2">
     <f:param value="#{pageFlowScope.var1}" id="p1"/>
     <f:param value="#{pageFlowScope.var2}" id="p3"/>
</h:outputFormat>

Implement a custom autosuggest filter for an ADF table

The purpose is to create a custom filter with autosuggestion feature on an ADF table column.

The idea is to have a couple of buttons in the table header to make the filter visible, and to disable and clear the filter itself:


After the filter is enabled, the text box appears, and as soon as the user starts typing, the autosuggestion kicks in:



This is what we need on the page/fragment:

A button to activate the filter (i.e. make the filter visible):

<af:commandButton text="Filter"
   id="cbFilter" rendered="true" partialSubmit="true"
   disabled="#{pageFlowScope.filter}" styleClass="btn">
 <af:setActionListener from="true" to="#{pageFlowScope.filter}"/>
</af:commandButton>

A button to deactivate and clear the filter:

<af:commandButton text="#{vehicleBundle.SELECTIVE_TABLE_FILTER_DISABLE}"
     id="cbDFilter" rendered="true"
     partialSubmit="true"
     actionListener="#{viewScope.vehicleBean.resetTableFilter}"
     disabled="#{!pageFlowScope.filter}"
     styleClass="btn">
  <af:setActionListener from="false"
      to="#{pageFlowScope.filter}"/>
</af:commandButton>

This is the table containing the filter: in the column we want to filter, we have an af:inputText component with autosuggest behavior operation, and two buttons to perform the filtering and clear the filter.
<af:table value="#{bindings.vehicleList1.collectionModel}" var="row"
     rows="#{bindings.vehicleList1.rangeSize}" fetchSize="5"
     varStatus="vs" partialTriggers=":cbDFilter :cbFilter"
     filterModel="#{bindings.vehicleListQuery.queryDescriptor}"
     queryListener="#{bindings.vehicleListQuery.processQuery}"
     filterVisible="#{pageFlowScope.filter}" id="listVehicles" [ . . . ]>
[ . . .]
<af:column filterable="true" filterFeatures="caseInsensitive"
     headerText="#{vehicleBundle.VEHICLE_DESC}" id="cVD">
 <f:facet name="filter">
  <af:panelGroupLayout id="pgAS" layout="vertical">
    <af:outputText value="#{vehicleBundle.FILTER_VEHICLES}" id="oFMsg"/>
    <af:panelGroupLayout id="pgFL" layout="horizontal">
    <af:inputText id="itFL"
         value="#{vs.filterCriteria.vehicleDescription}"
         autoSubmit="true" usage="search">
     <af:autoSuggestBehavior suggestItems="#{viewScope.vehicleBean.suggestedVehicles}"/>
    </af:inputText>
    <af:commandButton partialSubmit="true" id="cbexecute" text="Filter"/>
    <af:commandButton partialSubmit="true" id="cbclear" text="Clear"
          actionListener="#{viewScope.vehicleBean.resetTableFilter}"/>
    </af:panelGroupLayout>
  </af:panelGroupLayout>
 </f:facet>
</af:column>

In this case, we need the item type returned from the iterator to expose an attribute called 'vehicleDescription'.
  Since the table is supposed to show a collection of Vehicle objects, the Vehicle class needs to have a 'vehicleDescription' property (or a getter method called 'getVehicleDescription').

  So the page definition for this page/fragment will contain something like this:

<tree IterBinding="vehiclesIterator" id="vehicleList1">
      <nodeDefinition DefName="com.test.Vehicle"
                      Name="vehicleList10">
        <AttrNames>
          <Item Value="id"/>
          <Item Value="vciIdentifier"/>
          [. . .]
          <Item Value="vehicleDescription"/>
        </AttrNames>
      </nodeDefinition>
    </tree>

 [. . .]
 
 <searchRegion Binds="vehiclesIterator" Criteria=""
     Customizer="oracle.jbo.uicli.binding.JUSearchBindingCustomizer"
     id="vehicleListQuery"/>
 
Here are the methods needed:
public List suggestedVehicles(FacesContext facesContext,
                                 AutoSuggestUIHints autoSuggestUIHints) {
        // Add event code here...
        List<SelectItem> suggestions =
            autoSuggestIterator("vehiclesIterator",
                                         "vehicleDescription",
                                         autoSuggestUIHints.getSubmittedValue());
        return suggestions;
    }

    public void resetTableFilter(ActionEvent actionEvent) {
        // Add event code here...
        resetTableFilter("listVehicles");
    }
 
    public List<SelectItem> autoSuggestIterator(String iterator, String listValue, String input){
        // Add event code here...
        DCIteratorBinding binding = ADFUtils.findIterator(iterator);
        int rangeSize = binding.getRangeSize();
        binding.setRangeSize(1000);
        List suggestionList = ADFUtils.attributeListForIterator(iterator, listValue);
        List<SelectItem> suggestions = new ArrayList<SelectItem>();

        for (int i = 0; i < suggestionList.size(); i++) {
            if (suggestionList.get(i).toString().toUpperCase().contains(input.toUpperCase())) {
                suggestions.add(new SelectItem(suggestionList.get(i)));
            }
        }
        binding.setRangeSize(rangeSize);
        return suggestions;
    }
 
   public List attributeListForIterator(DCIteratorBinding iter,
                                         String valueAttrName) {
        List attributeList = new ArrayList();
        for (Row r : iter.getAllRowsInRange()) {
            attributeList.add(r.getAttribute(valueAttrName));
        }
        return attributeList;
    }
 
   public void resetTableFilter(String tableId) {

        UIComponent uiComponent = JSFUtils.findComponentInRoot(tableId);
        RichTable table = (RichTable)uiComponent;

        FilterableQueryDescriptor queryDescriptor =
            (FilterableQueryDescriptor)table.getFilterModel();
        if (queryDescriptor != null &&
            queryDescriptor.getFilterCriteria() != null) {
            queryDescriptor.getFilterCriteria().clear();
            table.queueEvent(new QueryEvent(table, queryDescriptor));
        }
    }

Monday, 23 June 2014

How to add multilingual support to a WebCenter Portal Framework application

These are our requirements:
  • The Login/Error pages need to be localized according to the browser's locale.
  • The user needs to has preferred locale associated to him (in a session variable)
  • When the user logs in, the preferred locale needs to be set as the preferred locale in the portal application: if the the preferred locale is not among the supported locales, the application default locale is applied.
  • The user needs to be able to switch locale at any moment from a dropdown menu.
  • The new locale needs to be set as the user preferred locale.
  • Both the resource bundles and the pages/fragments to be localized are not located in the portal application itself, but inside ADF shared libraries consumed from the portal application.
The starting point for the language selection solution is Frank Nimphius chapter 18 of the ADF 11g Oracle Fusion Developer Guide, and this blog post from A-Team's Martin Deh).

Description of the general solution
  • Create and register several resource bundles for the application, one for each language we need to support (plus the default bundle).
  • Define and register a session scoped bean, responsible of holding the selected locale.
  • Define and register a global custom Phase Listener, which before every RENDER_MODEL_ID phase accesses the bean, and sets the application locale to the selected one.
  • Create a portal page which will provide the user with the functionality to modify the current locale from a dropdown menu.
  • Localize the portal navigation model.
  • Set additional localization properties.
So, let's crack on with it!

1. Definition and registration of the resource bundles

Inside a separate ADF application, we define the resource bundles, one in each language we need to support. In our specific case, we support German and English languages, and so we will have 3 bundles defined:
  • one with _de suffix (es. diagnosticsBundle_de.properties)
  • one with _en suffix (es. diagnosticsBundle_en.properties)
  • one with no suffix at all (es. diagnosticsBundle_de.properties). This is selected in case none of the previous is matched.
For more information about resource bundle matching, click here.

We define one more properties file, called language.properties: this fil will store a comma separated list of supported languages for the application, and the default language for the application:

SUPPORTED_LOCALES=de,en
DEFAULT_LOCALE=de

In our case, these bundles are defined inside a separate ADF application, that is then packaged into an ADF LIbrary JAR file:

<SCREENSHOT OF THE APP IN JDEV>

If your bundles need to be defined in the portal application itself, the procedure is the same.

The next step is to register the supported languages and resource bundles is WEB-INF/faces-config.xml :

<?xml version="1.0" encoding="windows-1252"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee">
  <application>
 <default-render-kit-id>oracle.adf.rich</default-render-kit-id>
 <resource-bundle>
   <base-name>com/example/common/bundles/applicationBundle</base-name>
   <var>applicationBundle</var>
 </resource-bundle>
 <resource-bundle>
   <base-name>com/example/common/bundles/errorBundle</base-name>
   <var>errorBundle</var>
 </resource-bundle>
 <locale-config>
   <default-locale>de</default-locale>
   <supported-locale>en</supported-locale>
   <supported-locale>de</supported-locale>
 </locale-config>
  </application>
</faces-config>

You can define as many resource bundles as you want in here.

Of course the <var> element defines the name the bundle will be referred by in the pages/fragments of the application:

<af:outputText value="#{applicationBundle.SECTION_TITLE}" id="ot4"/>

2. Definition of the Change Language page

To introduce the Change Language functionality, we need to put the following code in a page/fragment of our portal application:
<af:selectOneChoice label="#{applicationBundle.SELECT_LANGUAGE}" id="localeSelector"
      value="#{localeBean.selectedLocale}" valuePassThru="false"
      binding="#{localeBean.langSelector}">
 <f:selectItems value="#{localeBean.suppLocales}" id="si1"/>
</af:selectOneChoice>

<af:commandButton text="#{applicationBundle.CHANGE_LANGUAGE}" id="cb1" 
      actionListener="#{localeBean.saveLanguageSettings}"/>

The EL expressions will be clearer in a bit, once we define the localeBean and we show it's content;

3. Definition of the Locale bean

In the portal application, open the WEB-INF/adfc-config.xml file and define a new session scoped managed bean:

<managed-bean>
  <managed-bean-name>localeBean</managed-bean-name>
  <managed-bean-class>com.example.portal.beans.LocaleBean</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

This bean will look like this:
/** 
 * The constructor reads the language.properties file and set the supported languages and the default language.
 */    
public LocaleBean(){
 ResourceBundle labels = ResourceBundle.getBundle("com.example.common.bundles.language");
 languages = labels.getString("SUPPORTED_LOCALES").split(",");
 defaultLocale = labels.getString("DEFAULT_LOCALE");
}

/**
 * Gets the list of the locales supported from the application.
 * @return the List of supported locales.
 */
public List getSuppLocales() {
 suppLocales = new ArrayList();
 for(String lang : languages){
  Locale locale = new Locale(lang);
  SelectItem item = new SelectItem(locale, locale.getDisplayLanguage(locale));
  suppLocales.add(item);
 }
 return suppLocales;
}

/**
 * Changes the current locale into the selected one.
 *
 * @param language the new locale to be set.
 */
private void changeLocale(String language) {
 Locale newLocale = new Locale(language);
 FacesContext fctx = FacesContext.getCurrentInstance();
 fctx.getViewRoot().setLocale(newLocale);
}

/**
 * Checks whether the preferred language (set in OID) is among the supported ones.
 *
 * @return true if the user preferred language is supported, false otherwise.
 */
private boolean isPreferredLanguageFound(){
 preferredLanguage = JSFUtils.resolveExpression("#{sessionScope.userInfoBean.preferredLanguage}");
 if(preferredLanguage == null){
  return false;
 }
 List locales = getSuppLocales();
 for(SelectItem l : locales){
  Locale loc = (Locale)l.getValue();
  if(loc.getLanguage().equals(preferredLanguage.toString())){
   return true;
  }
 }
 return false;
}

/**
 * This method is called to set the default language of the portal.
 * If the user has a preferred language in OID, that language is set, otherwise
 * the defaul language of the application (read from language.properties) is set.
 */
public void setDefaultLocale(){
 if(isPreferredLanguageFound()){
  changeLocale(preferredLanguage.toString());
 }else{
  changeLocale(defaultLocale);
 }
}

/**
 * This method is responsible for setting the application locale to the one
 * currently selected from the dropdown menu.
 * It then sets the selected language in OID, and in Autoaid.
 *
 * @param actionEvent the Action event.
 */
public void saveLanguageSettings(ActionEvent actionEvent) {
 selectedLocale = (Locale)langSelector.getValue();
 FacesContext fctx = FacesContext.getCurrentInstance();
 fctx.getViewRoot().setLocale(selectedLocale);

 //Set the language as the preferred one
 [...]

}

As soon as the bean is instantiated, the supported languages and the default language are read from the language.properties and stored in the respective fields.

The dropdown menu is populated with the list retrieved by the getSuppLocales() method, which is retrieving the supported languages extracted from the constructor.

As you may have noticed, the list of supported locales is also defined in faces.config.xml, so the lines 5-7 of the above code could be replaced with:

FacesContext.getCurrentInstance().getApplication().getSupportedLocales();
FacesContext.getCurrentInstance().getApplication().getDefaultLocale();

This lists both supported languages and the default language from the faces-config.xml file instead of the custom properties file.
However, in more complex cases where the both resource bundles and the pages/fragments to be localized are packaged into ADF shared libraries consumed from the mail portal application, this may not work. So in this case I prefer to define the language.propeties file, which does not lose in flexibility (to modify these settings would imply a redeploy of the application anyway).

The selectedLocale variable holds the locale currently selected from the dropdown menu, and the button executes the saveLanguageSettings() method which sets the local to the selected one.

4. Definition of a global PagePhaseListener

No, we have one problem: what we have built so far would work for a single request (when we hit the Change Language the button), but at the next action which involves a page refresh, the browser locale would be set automatically in the FacesContext, losing the selection we made.

For this reason, we need to introduce a global PagePhaseListener, which is responsible for intercepting a specific phase of the lifecycle (the RENDER_MODEL_ID phase) before the page is actually rendered, lookup the selected locale in our localeBean, and force this locale into the FacesContext.

First thing, we need to create adf-settings.xml file into the .adf/META-INF folder in the application root:

<?xml version="1.0" encoding="US-ASCII" ?>
<adf-settings xmlns="http://xmlns.oracle.com/adf/config">
 <adfc-controller-config xmlns="http://xmlns.oracle.com/adf/controller/config">
   <lifecycle>
  <phase-listener>
    <listener-id>portalPhaseListener</listener-id>
    <class>com.example.portal.CustomPhaseListener</class>
  </phase-listener>
   </lifecycle>
 </adfc-controller-config>
</adf-settings>

The beforePhase method will need to be define as follows:

import oracle.adf.controller.v2.lifecycle.ADFLifecycle;
import oracle.adf.controller.v2.lifecycle.PagePhaseEvent;
import oracle.adf.controller.v2.lifecycle.PagePhaseListener;

import oracle.webcenter.navigationframework.ResourceNotFoundException;
import oracle.webcenter.portalframework.sitestructure.SiteStructure;
import oracle.webcenter.portalframework.sitestructure.SiteStructureContext;

public class CustomPhaseListener implements PagePhaseListener {

public CustomPhaseListener() {
 super();
}

public void afterPhase(PagePhaseEvent pagePhaseEvent) {
}


public void beforePhase(PagePhaseEvent event) {
 Integer phase = event.getPhaseId();
 if (phase.equals(ADFLifecycle.PREPARE_MODEL_ID)) {
  FacesContext fctx = FacesContext.getCurrentInstance();
  LocaleBean localeBean =
   (LocaleBean) fctx.getApplication().evaluateExpressionGet(fctx, "#{localeBean}", Object.class);
  Locale selectedLocale = localeBean.getSelectedLocale();
  UIViewRoot uiViewRoot = fctx.getCurrentInstance().getViewRoot();

  //if the page is login or error, don't apply the locale change logic
  if(!uiViewRoot.getViewId().contains("login.jspx") && !uiViewRoot.getViewId().contains("error.jspx")){
   if (selectedLocale == null) {
    localeBean.setDefaultLocale();
   } else {
    uiViewRoot.setLocale(selectedLocale);
   }

   //refresh the Navigation model
   try {
      SiteStructureContext ctx = SiteStructureContext.getInstance();
      SiteStructure model = ctx.getDefaultSiteStructure();
      model.invalidateCache();
   }
   catch (ResourceNotFoundException rnfe) {
      rnfe.printStackTrace();
   }
  }

 }
}

From the logic above, you can see how the logic is applied only if the page is not the login page or the error page: in that case the browser locale should prevail.

If we are in any other page, the phase listener extracts the current view root from th FacesContext, then checks whether there is a locale already selected in the localeBean: in that case the locale is applied to the view root, otherwise the setDefaultLocale() method is called: this method is responsible for setting the language to the user preferred language (if this is in the list of the supported locales) or to the default application locale (the one we read from language.properties).

The last section is necessary to refresh the navigation model of the portal application when the locale is changed: these lines use the WebCenter Portal Framework Navigation APIs. This portion of code will be clearer in the next section.

5. Localization of the portal Navigation Model

One of the core points of this article is abaout localizing the portal navigation model.

As Martin Deh's article explains, the navigation model supports localization of certain textual strings like Title for example: so defining the page titles in the resource bundles would do the trick.

However, this would not work in case the navigation model is based on the portal page hierarchy (our case indeed):

<<SCREENSHOT>>

In our approach, for every page in the navigation model we need to access its page definition file and set the <parameter> element named 'page_title' with a resource bundle key instead of the hardcoded value.

For example:

<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel" 
                   version="11.1.1.61.92" 
                   id="diagnosticsPageDef"
                   Package="oracle.webcenter.portalapp.pages">
  <parameters>
  [...]
    <parameter id="page_title" value="DIAGNOSTICS"/>
  </parameters>
  [...]  

It means that in the resource bundle we are supposed to have a key named DIAGNOSTICS, with the actual title translation.

Then we need to adapt the code which actually displays the navigation model. Usually, this code is defined inside the portal page template(s).

An extract of a possible code to display the navigation model is:


<c:set var="navNodes" value="${navigationContext.defaultNavigationModel.listModel['startNode=/, includeStartNode=false']}" scope="session"/>
<af:panelGroupLayout styleClass="nav" id="pt_pgl8">
  <ul class="belt">
 <c:forEach var="menu" varStatus="vs" items="${navNodes}">
  <li>
     <a href="/myApplicationContextRoot${menu.goLinkPrettyUrl}">
     ${applicationBundle[menu.title]}
     </a>
   </li>
 </c:forEach>
  </ul>
</af:panelGroupLayout>

So when we change the locale, this portion of code we defined in our CustomPhaseListener class is there to make sure that the navigation model refreshes appropriately:

SiteStructureContext ctx = SiteStructureContext.getInstance();
SiteStructure model = ctx.getDefaultSiteStructure();
model.invalidateCache();

See here the documentation for the navigation model EL APIs and here for the documentation about how to visualize the portal navigation.

6. Additional Localization properties

It's possible to add further localization properties (like number grouping separator or decimal separator, currency codes, date format, timezone etc) in the WEB-INF/trinidad-config.xml file. for example:

<number-grouping-separator>
 #{view.locale.language=='de' ? '.' : ','}
</number-grouping-separator>

<!-- Set the decimal separator to comma for German -->
<!-- and period for all other languages -->
<decimal-separator>
 #{view.locale.language=='de' ? ',' : '.'}
</decimal-separator>

For further details, refer to the documentation here.

Tuesday, 15 April 2014

Redirect to servlet URL in a new browser window

      
Object contextPath = JSFUtils.resolveExpression("#{request.contextPath}");
String servletPath = contextPath + "/livedata";
try {
    FacesContext context = FacesContext.getCurrentInstance();
    ExtendedRenderKitService erks = Service.getRenderKitService(context, ExtendedRenderKitService.class);
    String script = "window.open('" + servletPath + "', '', 'location=0, status=0, resizable=1, scrollbars=0');";
    erks.addScript(FacesContext.getCurrentInstance(), script);
} catch (Exception e) {
    throw new FacesException("Redirection failed");
}

Wednesday, 26 February 2014

Executing an ADF operation binding programmatically

It's often needed to invoke an operation binding inside a taskflow, but sometimes the binding container that we expect to be ready for us to reference, is not ready for us yet.

If the taskflow is already showing a view activity, then the bindings variable will be populated with the binding container specific for the fragment, so it will be possible to lookup a binding programmatically:

BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry(); 
OperationBinding method = bindings.getOperationBinding("methodAction");  
Object result = method.execute();  

Otherwise we can use the findOperation utility method defined inside the commonly used ADFUtils class:

OperationBinding getTaskoperation = ADFUtils.findOperation("setVinAndVehicleIdForVehicle");
getTaskoperation.getParamsMap().put("instanceId", instanceId);
getTaskoperation.getParamsMap().put("autoaidVehicleId", autoaidVehicleId);
Object result = getTaskoperation.execute();

Now let's consider this scenario:

Inside a taskflow we use a method call (highlighted in the figure below) that invokes an operation binding, which is defined in the bindings section of fragment (the third activity in the figure below) that is still not shown:


In case we are inside a taskflow, but the fragment containing the operation bindings we need has not been shown yet, then the solution it's a bit more tricky, as the call above to getOperationBinding will return null: the binding container we need it's not been created yet.

In this case, we can access the data variable via EL, that gives us full access to all binding containers across the application: it can be a binding container defined for a fragment, a page or the binding container defined for an method call activity inside a taskflow:

JUFormBinding fb =
 (JUFormBinding) ADFUtils.resolveExpression("#{data.com_trw_id_adapters_scanResultsLivePageDef}");
JSFUtils.setExpressionValue("#{sessionScope.diagSession.diagnosticState}", state);
OperationBinding opb = fb.getOperationBinding("getEcusList");
opb.getParamsMap().put("session", JSFUtils.resolveExpression("#{sessionScope.diagSession}"));
opb.getParamsMap().put("diagnosisId", state.getCurrentDiagnosisId());
opb.getParamsMap().put("modifyTime", null);
opb.getParamsMap().put("weightThreshold", null);
Object result = opb.execute();

To figure out the correct EL expression you need to pass to 'resolveExpression' in order to retrieve the JUFormBinding object, then you need to go in DataBindings.cpx file; in here you will find 2 sections, one (pageMap) mapping the page/fragment/tf activity to an ID, and another (pageDefinitionUsages), mapping this ID to the fully qualified path of the page definition file:

<pageMap>
  [...]
  <page path="/WEB-INF/taskflows/connectionStateTF.xml#autoaidWS-connectionStateTF@getCurrentStatus"
           usageId="com_trw_id_adapters_connectionStateTF_connectionStateTF_getCurrentStatusPageDef"/>
 
  <page path="/fragments/ws/scanResultsLiveTF/scanResultsLive.jsff"
           usageId="com_trw_id_adapters_scanResultsLivePageDef"/>
  [...]
</pageMap>

<pageDefinitionUsages>
  [...]
  <page id="com_trw_id_adapters_connectionStateTF_connectionStateTF_getCurrentStatusPageDef"
           path="com.trw.id.adapters.autoaid.taskflows.connectionStateTF_connectionStateTF_getCurrentStatusPageDef"/>
   
  <page id="com_trw_id_adapters_scanResultsLivePageDef"
           path="fragments.ws.scanResultsLiveTF.scanResultsLivePageDef"/>
  [...]
</pageDefinitionUsages>

As you can see, the method call operation is mapped using the pattern:
<fragment or page name>#<taskflow ID>@<operation name>

What we actually need is the usageId itself (e.g. com_trw_id_adapters_scanResultsLivePageDef) and build the EL expression prefixing the string 'data.' to it:

#{data.com_trw_id_adapters_scanResultsLivePageDef}

Friday, 21 February 2014

Create ADF operation binding code in JDeveloper in a quick way

Let's assume we have an operation exposed in our data control, that we need to invoke programmatically from our managed bean that is behind the fragment we are working on; the following is a quick trick to make JDeveloper generate the operation binding access code for us (in this case, we want to execute the 'retrieveUserInfo' operation):



The first step is to drag and drop the operation into our page or fragment, selecting Method > ADF Button:



Once we do this, JDeveloper will generate the corresponding binding for us in the page Definition file for the page/fragment, and then finally it creates a commandButton component on the page, having its action listener pointing to the newly created binding.



Once the commandButton has been created, right click on the component, and select 'Create Method binding for action', then selects the managed bean we want to create the code into:






If we have a look to the bean, we will notice that JDeveloper has generated the necessary code for us to be invoked.

BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();
OperationBinding operationBinding = bindings.getOperationBinding("retrieveUserInfo");
operationBinding.getParamsMap().put("username", userName);
operationBinding.execute();


At this point we can remove the commandButton component from the page, deleting it manually from the code; please DO NOT DELETE IT USING THE STRUCTURE PANEL as it will delete the binding as well!

ADF Taskflow conditional activation

Sometimes is quite useful to make sure the taskflow is actually executed under a particular condition. Think about a panelTabbed component with 2 showDetailItem children, each of which consumes a taskflow inside a region:

<af:panelTabbed dimensionsFrom="auto" id="panelTabbed">
 <af:showDetailItem id="sdi2" text="First tab">
    <af:region id="r1" value="#{bindings.customTF1.regionModel}"/>
    <af:setPropertyListener from="TAB1" to="#{pageFlowScope.tabClicked}" 
                               type="disclosure"/>
 </af:showDetailItem>
 <af:showDetailItem id="sdi1" text="Second tab">
  <af:region id="r2" value="#{bindings.customTF2.regionModel}"/>
  <af:setPropertyListener from="TAB2" to="#{pageFlowScope.tabClicked}" 
                             type="disclosure"/>            
 </af:showDetailItem>
</af:panelTabbed>
By default, when this component is rendered, both taskflows are executed, whether they are in the disclosed showDetailItem or not.

Since the execution of a taskflow may imply a significant overhead for our application, we may decide that the taskflow needs to be executed only when the tab containing it is actually disclosed.

For this purpose, we can set the the activation propery of the taskflow binding to 'conditional', instead of 'deferred' (default). In order to do that, we need to provide a condition for the taskflow to be activated: for this purpose we can use a setPropertyListener operation inside each showDetailItem component to set a property called 'tabClicked' in the pageFlowScope, the value we set obviously is different for every tab. This property will be used in the EL expression to check the taskflow activation condition.
In the page bindings where the panelTabbed (and the regions) are used, make sure to setup the taskflow bindings attributes as described before:

<taskflow activation="conditional" active="#{pageFlowScope.tabClicked=='TAB1'}" 
          id="customTF1" 
          taskflowid="/WEB-INF/taskflows/ws/customTF1.xml#customTF1" 
          xmlns="http://xmlns.oracle.com/adf/controller/binding">
  <parameters>
 [...]
  </parameters>
</taskflow>
<taskflow activation="deferred" active="#{pageFlowScope.tabClicked=='TAB2'}" 
          id="customTF2" 
          taskflowid="/WEB-INF/taskflows/ws/customTF2.xml#customTF2" 
          xmlns="http://xmlns.oracle.com/adf/controller/binding">
  <parameters>
 [...]
  </parameters>
</taskflow>


At this point, the taskflows should be invoked only when they are actually shown on the page (i.e. when the tab containing it is disclosed).

Wednesday, 19 February 2014

Usage of JavaScript in ADF Faces Rich Client Applications: guidelines and best practices

Using JS into ADF application is often considered a controversial topic.

I this white paper of ADF Design Fundamentals series, Franck Nimphius provides a guideline about how to properly use JS into an ADF application:

http://www.oracle.com/technetwork/developer-tools/jdev/1-2011-javascript-302460.pdf

The bottom line is:

If JavaScript is used, developers are encouraged to only use public ADF Faces client framework APIs instead of direct browser DOM manipulation and to stay away from using ADF Faces JavaScript objects stored in internal package hierarchies. 

This list of 10 best practices is extracted from the white paper above:

  • Best Practice 1: An oven alone doesn’t make a cook. Developers should ensure they understand the ADF Faces client architecture before using JavaScript in ADF Faces applications. 
  • Best Practice 2: Protected, package, or private methods and variables that DOM inspection tools like Firebug show for ADF Faces components and events should not be used in custom ADF Faces application development. 
  • Best Practice 3: Application developers should ensure that a component exists on the client before accessing it from JavaScript. ADF Faces client component objects are created by setting the component clientComponent property to true or adding an af:clientListener tag. 
  • Best Practice 4: Developers should not hardcode any client component id that shows in the generated HTML output for a page into their JavaScript functions. Client id values may change even between runs of a view. Instead developers can dynamically determine the client Id by a call to the ADF Faces component's getClientId method in a managed bean. 
  • Best Practice 5: Application developers should call cancel()on all events that don't need to propagate to the server. 
  • Best Practice 6: Client-to-server calls should be used sensibly to reduce the amount of network roundtrips and thus to prevent slow performance due to network latency. 
  • Best Practice 7: No knowledge about framework internal implementation details should be used directly in JavaScript code. Instead, public APIs and constants should always be used. 
  • Best Practice 8: JavaScript exceptions should be handled gracefully and not just suppressed. 
  • Best Practice 9: The framework default behavior should be respected. The rich client component architecture and its APIs are performance optimized. Component functionality therefore should not be changed by call to the client component prototype handler. 
  • Best Practice 10: JavaScript should not be used for implementing application security.

How to enable ADF Diagnostic Logging in JDeveloper

I usually refer to this presentation of Steve Muench:

in this presentation many topics related to ADF debugging/logging are covered, like:

  • Enable Diagnostic Logging (covered in this article)
  • Debug with the Business Components Tester
  • Create a Command-Line Test Client Program
  • Export Debugger Call Stack and Exact JDev/ADF Version
  • Set Up and Use Oracle ADF Source for Debugging
  • Conditional breakpoint expressions
  • Breakpoints on Task Flow Activities
  • ADF Structure and ADF Data Windows
  • Breakpoint on Action Bindings in Page Definition
  • EL Evaluator Window
  • Logging Executed Queries and Fetched Rows

Also this article from Shay Shmeltzer gives a very good overview of the ADF Debugger features (setting breakpoints on various ADF artifacts, using the ADF structure window, ADF Data window and EL Evaluater window).

This article only focuses on enabling the diagnostic logging.

At first, it is useful to make sure the log is saved to the file system, for further analysis:



First setup the log viewer:




Then from the project properties, create a new Run/Debug profile:



Give the profile a name:



Add the string '–Djbo.debugoutput=console' to the Java Options:



Finally, make sure you are using the new Run/Debug configuration:


Get selected rows of ADF Table

Assuming you expose a binding (called 'resultTable') of the table component in a managed bean:
For multiple row selection (assuming a RichTable reference called myDataTable, based on a collection of MyObject objects):

// get the selected rows from a table component  
public void getMultipleRows(ActionEvent actionEvent) {
     RichTable table = getMyDataTable();
       for (Object facesRowKey : table.getSelectedRowKeys()) {
            table.setRowKey(facesRowKey);
            Object o = table.getRowData();
            MyObject selectedItem = (MyObject)o;
            if (selectedItem != null) {
                 // process the item
       }
   }
}
For single selection:
public Object getSelectedRow(RichTable table) {
   Object _selectedRowData = table.getSelectedRowData();
   JUCtrlHierNodeBinding _nodeBinding = (JUCtrlHierNodeBinding)_selectedRowData;
   return _nodeBinding.getRow();
}

So we can define a Selection Listener for the table, and this way we can get the source table component from the event itself:
// to get the selected Rows 
public void itemSelected(SelectionEvent selectionEvent) {
     RichTable table = (RichTable)selectionEvent.getSource();
     DCDataRow dcrow = (DCDataRow)getSelectedRow(table);
     MyObject selectedItem = (MyObject)dcrow.getDataProvider();
     // TODO ...
}

If the table is backed by a View Object, we can cast the selected row to a ViewRowImpl object:
public void itemSelected(SelectionEvent selectionEvent) {
     RichTable table = (RichTable)selectionEvent.getSource();
     ViewRowImpl row = (ViewRowImpl)getSelectedRow(table);
     if (row != null) {
        String attr1 = (String)row.getAttribute("attr1");
        String attr2 = (String)row.getAttribute("attr2");
        // ...
   }
}   

Check OID connection programmatically

This snippet of code allow us to check whether the connection to an OID Identity store is available.

import oracle.security.jps.JpsContext;
import oracle.security.jps.JpsContextFactory;
import oracle.security.jps.JpsException;
import oracle.security.jps.service.idstore.IdentityStoreService;

/**
  * Method to check that I can connect to OID Server. If I can get a
  * valid Identity Store means that there is a valid connection.
  */
private boolean oidUpAndRunning() {
    logger.info("Checking that OID is up and running...");
    try {
 getStoreService().getIdmStore();
 return true;
    } catch (JpsException e) {
 e.printStackTrace();
 return false;
    }
}

/**
 * Get an Identity Store Service from the JPS Context.
 */
public IdentityStoreService getStoreService() throws JpsException {
    JpsContextFactory ctxf = JpsContextFactory.getContextFactory();
    JpsContext ctx = ctxf.getContext();
    return ctx.getServiceInstance(IdentityStoreService.class);
}

Getting data from ADF bindings programmatically

These are some useful code snippets to work with ADF bindings programmatically:

// get the binding container  
BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();  
  
// get an ADF attributevalue from the ADF page definitions  
AttributeBinding attr = (AttributeBinding)bindings.getControlBinding("test");  
attr.setInputValue("test");  
  
// get an Action or MethodAction  
OperationBinding method = bindings.getOperationBinding("methodAction");  
method.execute();  
List errors = method.getErrors();  
  
method = bindings.getOperationBinding("methodAction");  
Map paramsMap = method.getParamsMap();  
paramsMap.put("param","value")  ;        
method.execute();  


// Get the data from an ADF tree or table  
DCBindingContainer dcBindings = 
        (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();  
  
FacesCtrlHierBinding treeData = (FacesCtrlHierBinding)bc.getControlBinding("tree");  
Row[] rows = treeData.getAllRowsInRange();  
  
// Get a attribute value of the current row of iterator  
DCIteratorBinding iterBind= (DCIteratorBinding)dcBindings.get("testIterator");  
String attribute = (String)iterBind.getCurrentRow().getAttribute("field1");  
  
// Get the error  
String error = iterBind.getError().getMessage();  

// refresh the iterator  
bindings.refreshControl();  
iterBind.executeQuery();  
iterBind.refresh(DCIteratorBinding.RANGESIZE_UNLIMITED);  
  
// Get all the rows of a iterator  
Row[] rows = iterBind.getAllRowsInRange();  
TestData dataRow = null;  
for (Row row : rows) {  
  dataRow = (TestData)((DCDataRow)row).getDataProvider();  
}  

// Get the current row of a iterator , a different way  
FacesContext ctx = FacesContext.getCurrentInstance();  
ExpressionFactory ef = ctx.getApplication().getExpressionFactory();  
ValueExpression ve = ef.createValueExpression(ctx.getELContext(), 
                "#{bindings.testIter.currentRow.dataProvider}", TestHead.class);  
TestHead test = (TestHead)ve.getValue(ctx.getELContext());  

 
// get the selected rows from a table component  
RowKeySet selection = resultTable.getSelectedRowKeys();  
Object[] keys = selection.toArray();  
List receivers = new ArrayList(keys.length);  
for ( Object key : keys ) {  
  User user = modelFriends.get((Integer)key);  
}  
  
// get  selected Rows of a table 2  
for (Object facesRowKey : table.getSelectedRowKeys()) {  
 table.setRowKey(facesRowKey);  
 Object o = table.getRowData();  
 JUCtrlHierNodeBinding rowData = (JUCtrlHierNodeBinding)o;  
 Row row = rowData.getRow();  
 Test testRow = (Test)((DCDataRow)row).getDataProvider() ;  
}  

Useful code snippets for ADF Security

Here are some useful code snippets related to ADF Security (thanks to Edwin Biemond blog):

// print the roles of the current user  
for ( String role : ADFContext.getCurrent().getSecurityContext().getUserRoles() ) {  
   System.out.println("role "+role);  
}  
  
// get the ADF security context and test if the user has the role users         
SecurityContext sec = ADFContext.getCurrent().getSecurityContext();  
if ( sec.isUserInRole("users") ) {  
}  

// is the user valid  
public boolean isAuthenticated() {  
 return ADFContext.getCurrent().getSecurityContext().isAuthenticated();  
}
  
// return the user  
public String getCurrentUser() {  
 return ADFContext.getCurrent().getSecurityContext().getUserName();  
}  

Friday, 14 February 2014

Showing popup in ADF during long running operations


An ADF application can trigger long running operations (DB queries, WS calls, etc), and this could cause user experience issues: user getting impatient, clicking everywhere and (possibly) crashing the application causing unpredictable behaviour (double submitting forms, modifying input data while running).

The following one is a solution proposed from Frank Nimphius in this article of ADF Code Corner: for every long running operation, we show an ADF popup (it's our choice if we want to make it modal or not), making a JS function call via the clientListener component.

The solution is based on 3 steps:

1) Create the popup component, making sure that contentDelivery is set to immediate (to render it when the page loads) and clientComponent is set to true (to make it accessible from JS):

<af:popup id="waitPopup" contentDelivery="immediate" clientComponent="true">
  <af:dialog id="waitDialog" type="none"
             title="#{resBundle.WAIT_POPUP_TITLE}" closeIconVisible="false">
    <af:panelGroupLayout id="pgl2" layout="vertical" halign="center">
     <af:image source="/images/scan.gif" id="i1"/>
     <af:outputText value="#{resBundle.WAIT_POPUP_MSG}" id="waitText"/>
    </af:panelGroupLayout>
  </af:dialog>
</af:popup>

2) Create JS code to disable the user input and open the popup; create a file named waitingpopup.js in a subfolder of public_html folder in your application, and write the following code inside it:
function enforcePreventUserInput(evt) {
    var popup = AdfPage.PAGE.findComponentByAbsoluteId('waitPopup');
    if (popup != null) {
        AdfPage.PAGE.addBusyStateListener(popup, handleBusyState);
        evt.preventUserInput();
    }
}

function handleBusyState(evt) {
    var popup = AdfPage.PAGE.findComponentByAbsoluteId('waitPopup');
    if (popup != null) {
        if (evt.isBusy()) {
            popup.show();
        }
        else if(popup.isPopupVisible()) {
            popup.hide();
            AdfPage.PAGE.removeBusyStateListener(popup, handleBusyState);
        }
    }
} 

After the file is saved, put the resource tag in the page/fragment containing the popup, using the correct path of the file created above (in this case the file has been created under public_html/scripts folder):
<af:resource type="javascript" source="/scripts/waitingpopup.js"/>

3) Trigger the popup by invoking the JS code above via clientListener:

<af:commandLink id="commandLink4" styleClass="btn btn-alt"
    disabled="#{isTaskRunning}"
    actionListener="#{bindings.longOperation.execute}"
    action="back"> 
  <af:clientListener method="enforcePreventUserInput" type="action"/> 
</af:commandLink>


What is not optimal in this solution is the part in which the JS retrieves the popup component by ID: this approach works smoothly if the popup is defined at top level (inside a JSF page or a JSF template: if the popup is defined inside a fragment, the ID for this component at runtime will be prefixed by the id of the region (something like 'r0:waitingPopup'), and in case the ADF taskflow containing this fragment is shown (for example) in a Portal page that is using a template, the component id is prefixed with the ID of the template as well; bottom line is, define this popup always at top level, or the code above won't always work.

Saturday, 5 November 2011

How to call JavaScript from a ADF Managed Bean

To run a JS function from our Managed Bean, first put the JS code in a <af:resource> component in our JSF page:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" 
           xmlns:f="http://java.sun.com/jsf/core" 
           xmlns:pe="http://xmlns.oracle.com/adf/pageeditor" 
           xmlns:cust="http://xmlns.oracle.com/adf/faces/customizable" 
           xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
<af:resource type="javascript">
    function myFunc() { [ any JS code ] };
</af:resource>

In the managed bean, put the following code in the appropriate method (e.g. in a action listener if we want to execute the JS code after a commandButton or commandLink is selected):
import javax.faces.context.FacesContext;
import org.apache.myfaces.trinidad.render.ExtendedRenderKitService;
import org.apache.myfaces.trinidad.util.Service;
[…]
FacesContext facesContext = FacesContext.getCurrentInstance(); 
ExtendedRenderKitService service = (ExtendedRenderKitService) org.apache.myfaces.trinidad.util.Service.getRenderKitService(facesContext, ExtendedRenderKitService.class);
service.addScript(facesContext, "myFunc();");

Accessing an ADF Operation binding programmatically

Developing an ADF application, it sometimes comes out we need to invoke programmatically (e.g. from a Managed Bean) a method exposed as operation binding in a ADF Data Control. To do this, we need to access the current binding container:

OperationBinding searchOp = ADFUtils.findOperation("searchBusinessUnits");
The ADFUtils.findOperation() method has the following definition:
public static OperationBinding findOperation(String operationName) {
OperationBinding op = getDCBindingContainer().getOperationBinding(operationName);
if (op == null) {
throw new RuntimeException("Operation '" + operationName +"' not found");
}
return op;
}

Unfortunately, when the previous code is executed in a method action invoked at page load time through an invokeAction binding, it happens that the reference to the binding container is null at this point. In this case, we need to access the binding container in a different way, using EL:

import oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding;

[. . .]

FacesCtrlActionBinding searchOp = null;
searchOp = (FacesCtrlActionBinding)JSFUtils.resolveExpression("#{data.portal_peoplefinder_resultsPageDef.searchBusinessUnits}");
Map opParams = searchOp.getParamsMap();
opParams.put("searchTerm", searchString);
Object result = searchOp.execute();

The pageDef excerpt of the page (or page fragment) containing the binding I need to access (in this case peopleFinder_resultsPageDef.xml ):

<bindings>
[...]
<methodAction id="searchBusinessUnits" InstanceName="ContentServicesDC.dataProvider"
DataControl="ContentServicesDC" RequiresUpdateModel="true"
Action="invokeMethod" MethodName="searchBusinessUnits" IsViewObjectMethod="false"
ReturnName="data.ContentServicesDC.methodResults.searchBusinessUnits_ContentServicesDC_dataProvider_searchBusinessUnits_result"/>
[...]
</bindings>

And the line of DataBindings.cpx regarding this binding:

<page id="portal_peoplefinder_resultsPageDef"
path="oracle.webcenter.portalapp.pagefragments.peoplefinder_resultsPageDef"/>

IMPORTANT: the EL expression we are using to retrieve the binding must be comply with the following format:

[“data”] + [id of the corresponding <page> entry in DataBindings.cpx] + [name of the binding we want to retrieve]

Executing custom code at page load time via ADF Data Control

This is a very common use case: invocation of some custom code at page load time.

Thanks to ADF Data Control capability, we can incapsulate our custom code in a method of a POJO, and finally expose this as a Data Control (it's worth reminding that it complies with JSR 227 specification, so it's actually adherent to Java standards).

As we have many flavours of bindings, we are going to show in this tutorial how to achieve the same result using an attribute binding, and then using a method binding.

For this tutorial JDeveloper 11.1.1.5.0 has been used.

As a first step, let's create a new Fusion Web Application (ADF) :



In the Model project we now create a new Java class, named PageLoadDC:




and expose this class as a Data Control:


After this, the Data Controls panel is populated with the new item:

And the DataControls.dcx file is updated with the following content:



Now let's work on the ViewController project: at first we create a new JSF page, named home.jspx:




To make JDeveloper create the necessary bindings for you in the pageDef file for the new page, just drag & drop the method binding from the DataControls panel onto the page, choosing ADF Output Text as display mode for this binding:




In this way, many things happen behind the scenes:

–        a new pageDef for home.jspx page is created

–        a new binding for the method exposed in the data control is created

–        an iterator has been defined for this binding, to access the results of this method call.



The generated code in the newly created pageDef is:



Running the application gives the expected result:



An interesting observation:

Having named the DC method with a name starting with 'get' (in this case 'getContentAtPageLoad'), this is interpreted by ADF as a getter of a property 'contentAtPageLoad', so the binding definition is really an attribute binding rather than a pure method binding, and in the home.jspx page the following EL expression is used to retrieve the value:
#{bindings.contentAtPageLoad.inputValue}

Now we add a new method in the DC, this time naming it in a different name, e.g. 'buildContentAtPageLoad':



and we expose the file as a DC again:



we can now notice how a method binding is actually created.

Now in the bindings box select the Plus icon to create a new binding, the select methodAction:



In the next screen just select the DC we just created, and the method name will appear in the 'Operation' dropdown list, then press OK:



As a result, a new binding is created in the pageDef file, and it appears in the 'Bindings' box in the page overview :



Now, to make this methodAction execute when the page is loaded, we just need to create an invokeAction to actually invoke it: in the 'Executables' panel in the 'Bindings' tab for the home.jspx page just click the Plus icon and select InvokeAction:



In the 'InsertInvokeAction' select the 'buildContentAtPageLoad' method and assign it an arbitrary ID, then click OK:



Then select the newly created invokeActionBinding, and in the property inspector set the 'Refresh' dropdown to 'Always':



In the JSPX page set the EL expression for the new binding like that:



Running the page again we get the expected result:



Easy as a pie.

You can download the example JDeveloper project here (remove the .pdf extension before unpacking).