Showing posts with label tutorial/howto. Show all posts
Showing posts with label tutorial/howto. Show all posts

Wednesday, 21 October 2015

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.

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.

Thursday, 13 February 2014

Apply SyntaxHiglighter to my Blogger template

To apply SyntaxHiglighter to my Blogger template: Go to Blogger Dashboard -> Template -> Edit HTML Right before the </body> tag, paste this:
<pre class="brush:xml">
<!-- SyntaxHighlighter starts -->
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shCore.css' rel='stylesheet' type='text/css'/>
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css' rel='stylesheet' type='text/css'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJava.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushSql.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushBash.js' type='text/javascript'/>
<script language='javascript'>
SyntaxHighlighter.config.bloggerMode = true;
SyntaxHighlighter.all();
</script>
<!-- SyntaxHighlighter ends -->
</pre>

Save the template. In the Post Editor, switch to the HTML mode and paste your escaped HTML code between the tags.

Use Quick Escape to convert your HTML to escape characters.

<pre class='brush:xml'>
// HTML Code here
</pre>
The available brushes: as3, bash, csharp, coldfusion, cpp, css, diff, erlang, groovy, jscript, java, javafx, perl, php, plain, powershell, phython, ruby, scala, sql, vb, xml
Also you can follow this tutorial:
http://www.stramaxon.com/2012/07/add-syntax-highlighter-to-blogger.html
To escape HTML code:
http://accessify.com/tools-and-wizards/developer-tools/quick-escape/default.php

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).

Friday, 11 March 2011

Smart GWT showcase

Here is a nice website...a big Smart GWT showcase application!

http://www.smartclient.com/smartgwt/showcase/#main

Smart GWT is a GWT-based framework that allows you to not only utilize its comprehensive widget library for your application UI, but also tie these widgets in with your server-side for data management. For a quick conceptual overview of Smart GWT, read this article, or visit the official site.

Smart GWT is based on the powerful and mature SmartClient library. In addition to Smart GWT LGPL, Pro & Enterprise editions of Smart GWT are available with additional features & tools, as well as commercial support.

Tuesday, 2 March 2010

CRUD generico con Java Reflection API, EclipseLink ed ICEFaces

Problema: abbiamo un nutrito insieme di tabelle anagrafiche con struttura simile (solitamente ID, DESCRIZIONE e pochi altri campi), e vorremmo offirire nella nostra applicazione Web le funzionalità CRUD su di esse. Quello che vogliamo assolutamente evitare è la replicazione del codice che offre queste funzionalità per ogni tabella da gestire: in sostanza, si desidera un approccio quanto più generico possibile al problema.


Voglio condividere con voi una soluzione a questo problema a mio avviso molto interessante, che fa uso estensivo delle Reflection API di Java, EJB 3.0, JPA (con EclipseLink 1.20 come persistence engine) per il backend, e IceFaces 1.8 per la parte grafica.


Sunday, 4 October 2009

Installazione di EclipseLink in OC4J

Dopo qualche mese di silenzio, eccovi un piccolo tutorial che ho avuto modo di creare qualche giorno fa; come accade spesso in questi ultimi tempi,  mi sono trovato a dover migrare alcune applicazioni JEE basate su motore di persistenza Toplink Essentials alla nuova versione EclipseLink su piattaforma Oracle AS 10.1.3.4 (OC4J). Al contrario di WebLogic, l'installazione di EclipseLink in OC4J è meno banale, e vale la pena riassumere gli step necessari per completarla (è stata presa come riferimento una sezione  del Wiki di EclipseLink). E' anche posssibile evitare l'installazione di EclipeLink, includendo la suddetta libreria nel file EAR della nostra applicazione: tale approccio è tuttavia sconsigliato, in quanto la bassa posizione del file eclipselink.jar sul ClassLoader impedirebbe ad altre applicazioni di poterlo utilizzare.

Download

    Innanzitutto occorre scaricare la distribuzione di EclipseLink, disponibile alla sezione downloads del sito ufficiale di EclipseLink. Una volta scaricato il file eclipselink-n.n.n.zip, estrarre da esso i due file

    - eclipselink-*\eclipselink\jlib\eclipselink.jar

    - eclipselink-*\eclipselink\jlib\jpa\javax.persistence_*.jar
    Creazione di una nuova shared-library


      In questo passo, andremo a creare una nuova shared-library versionata per EclipseLink, aggiungendo il riferimento ai file jar nei file XML di configurazione.

      In primo luogo, creare una nuova directory org.eclipse.persistence in OC4J_ROOT/shared-lib con una sottodirecotry n.n.n per questa specifica versione  - the jars will be placed and referenced here. Ad esempio:


      /opt/oc4j101n/j2ee/home/shared-lib/org.eclipse.persistence/n.n.n

      Quindi, copiare i due file jar estratti in precedenza in questa directory.

      Arrivati a questo punto, occorre modificare il file $OC4J_ROOT/config/server.xml, aggiungendovi il seguente elemento shared-library:


      <shared-library name="org.eclipse.persistence" version="1.1.0" library-compatible="true">
         <code-source path="/opt/oc4j101n/j2ee/home/shared-lib/org.eclipse.persistence/n.n.n/eclipselink.jar"/>
         <code-source path="/opt/oc4j101n/j2ee/home/shared-lib/org.eclipse.persistence/n.n.n/javax.persistence_*.jar"/>
         <import-shared-library name="oracle.jdbc"/>

      Nota: l'attributo path dell'elemento code-source prevede che TUTTO il path dei jar file venga specificato, altrimenti OC4J non troverà i file al successivo riavvio ed eliminerà la porzione di server.xml appena aggiunta (con l'ovvia conseguenza che le librerie non verranno caricate dal ClassLoader).

      Import della nuova shared library

        Adesso occorre aggiungere il seguente elemento import-shared-library nel file $OC4J_ROOT/config/system-application.xml, appena sopra le due shared library esistenti oracle.persistence e oracle.toplink.

         <imported-shared-libraries>
         <!-- new EclipseLink shared library -->
          <import-shared-library name="org.eclipse.persistence"/>
          <!-- existing shipped out-of-the-box libraries -->
          <import-shared-library name="oracle.toplink"/>
          <import-shared-library name="oracle.persistence"/>


        Infine, riavviare l'istanza OC4J per rendere effettive le modifiche.

        Nota: la procedura viene descritta per l'istanza HOME, ma è ripetibile per qualsiasi istanza <OC4J_INST>. Occorre preventivamente creare la cartella shared-lib sotto j2ee/<OC4J_INST> e copiarvi i file jar, oppure posso lasciarli nella cartella j2ee/home/shared-lib e farla puntare al file server.xml in j2ee/<OC4J_INST>/config.

        Thursday, 4 June 2009

        Web Services features di Oracle Data Integrator 10g



        Oracle Data Integrator supporta tre funzionalità principali riguardanti i Web Services:




        1. ODI Public Web Service: permette di invocare uno scenario ODI tramite chiamata ad un Web Service

        2. ODI Data Services: si tratta di Web Services, creati automaticamente da ODI, sovrastanti data store ODI di tipo DB (ad esempio una tabella, una vista o un altra sorgente dati registrata in ODI)

        3. Il tool ODIInvokeWebService, che, invocato all'interno di uno scenario ODI, mi permette di invocare un WS ed, eventualmente, utilizzare i dati di risposta in un flusso di integrazione.


        Le funzionalità 2 e 3 prevedono l’utilizzo di Axis2 (v1.2), un web services engine open source, distribuibile sotto forma di web application (WAR file) su un qualsiasi JEE container, nel nostro caso Weblogic 10.3.

        Monday, 25 May 2009

        Howto per le certificazioni SUN Java

        Condivido con voi un utile articolo di M.Marzocchi pubblicato su JavaPortal, di sicuro interesse per chi, come me, ha nella propria "TODO list" un esame di certificazione Java di SUN Microsystems (o la dobbiamo già chiamare Oracle?!?). L'articolo è di particolare utilità, vista la caoticità delle informazioni che circolano in rete sull'argomento, che rendono più complicata del dovuto la preparazione, o quantomeno l'approccio iniziale ad essa, dell'esame di certificazione.


        Sunday, 17 May 2009

        Provider di autenticazione RDBMS offerti da Weblogic Server 10.3


        In questo articolo vedremo come configurare ed utilizzare due meccanismi di autenticazione RDBMS (ovvero in cui le informazioni riguardanti utenti, gruppi e appartenenza degli utenti ai rispettivi gruppi, sono memorizzate su tabelle DB) offerti da WebLogic Server 10.3. Per una panoramica completa di queste feature di WLS si veda la documentazione ufficiale Oracle-BEA.


        Gli authentication provider di cui parleremo sono:



        • SQL Authenticator

        • Read Only SQL Authenticator

        Entrambi i provider offrono le funzionalità base di autenticazione, ma il primo offre anche la possibilità di gestire le utenze (creazione/eliminazione di utenti e ruoli, modifica password, associazione/eliminazione di un utente ad un ruolo).