Sunday, February 28, 2010

Setting Proxy Authentication in Java (weblogic.net.http.HttpUnauthorizedException: Proxy or Server Authentication Required)

weblogic.net.http.HttpUnauthorizedException: Proxy or Server Authentication Required
 at weblogic.net.http.HttpURLConnection.getAuthInfo(HttpURLConnection.java:284)
 at weblogic.net.http.HttpURLConnection.getInputStream(HttpURLConnection.java:455)
 at weblogic.net.http.SOAPHttpURLConnection.getInputStream(SOAPHttpURLConnection.java:36)
 at com.google.api.GoogleAPI.retrieveJSON(GoogleAPI.java:112)
 ... 45 more
I encountered the above error while attempting to test my Oracle ADF sample application that integrates with the Google Translate behind a proxy. I believe this error applies when your application tries to access the internet while behind a firewall that requires proxy authentication.

Below are the steps that resolved the issue above:
  1. Extend java.net.Authenticator class and override the getPasswordAuthentication() method.
  2. Set System properties for the http:proxyHost, http:proxyPort
  3. Set your custom authenticator above as the default authenticator.
  4. Modify the instantiation of the URL
Below is my custom authenticator:
package soadev.blogspot.googletranslateapi.common;

import java.net.Authenticator;
import java.net.PasswordAuthentication;

public class MyAuthenticator extends Authenticator {
    private String username;
    private String password;
    public MyAuthenticator(String username, String password){
        this.username = username;
        this.password = password;
    }
    public PasswordAuthentication getPasswordAuthentication () {
        return new PasswordAuthentication (username, password.toCharArray());
    }

}
Below are the code snippets (as it applies to my case) that illustrates the steps above:
    System.setProperty("http.proxyHost", "myproxy");             
    System.setProperty("http.proxyPort", "8080");
    Authenticator.setDefault (new MyAuthenticator("XXXXXX\\username","password"));
Below is how I instantiate the external URL to be accessed:
...
final URL url = new URL(null, "http://your_external_URL", new sun.net.www.protocol.http.Handler());
notes for readers of my blogpost on leveraging the google translate api: To run your test behind proxy, you need to modify the execute() method of the Translate class to reflect the proper way of instantiating the URL.

Cheers!
pino

Oracle ADF and Google Translate : Leveraging Google Translation Services in Oracle ADF Applications

An application that will be used in a multi-cultural and multi-language environment can leverage the free translation service of Google through Google Translate API.

This post recreated the Google Translate service using Oracle ADF Faces RC in JDeveloper 11g to demonstrate the simplicity and the possibilities of using the Google Translate API. Below are screen shots of our completed Google Translate API demo in ADF:
 
Just like the official Google Translate page, the screen shot above shows that we can choose the "from what" and "to what" language shall we translate. Invoking the "Translate" button will display the translated text without full submit of the page.
 
Invoking "Clear" button will reset the components.

To recreate this demo we need to do the following:
  1. Download the Java client API for using Google Translate.
  2. Create a new JDeveloper application with one view-controller project.
  3. Create the translate.jspx page
  4. Create the GoogleTranslateForm class (the backing bean of translate.jspx)
  5. Create and register a LanguageEnumConverter class

Download the Java client API for using Google Translate.

The unofficial Java client API is only about 44KB. We can download it from the following link: google-api-translate-java. To use it, we just simply include the google-api-translate-java.jar file into our application's classpath.

Create a new JDeveloper application with one view-controller project.

Create a new generic application with one view-controller project in which ADF Faces and ADF Page Flows project technologies are selected. The ADF Page Flows is optional, but we use it for consistency.
Below is a screen shot of my sample project structure:

Create the translate.jspx page

To create the translate.jspx page, we need to open adfc-config.xml, drop a view object, named it "translate", double click it to invoke the create page wizard. Basically our page will have the following components:
  • an inputText component to hold user input;
  • two selectOneChoice component to hold the Language From and Language To;
  • a button to to invoke the translation service; and
  • a button to reset the form
Below is my translate.jspx page:
<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
  <jsp:directive.page contentType="text/html;charset=UTF-8"/>
  <f:view>
    <af:document id="d1">
      <af:form id="f1">
        <af:panelGroupLayout layout="scroll"
                             xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
                             id="pgl2">
          <af:panelGroupLayout id="pgl7" layout="horizontal">
            <af:image source="/images/translate_logo.gif" id="i1"/>
            <af:activeOutputText value="API Demo in ADF" id="aot1"
                                 inlineStyle="font-size:medium;"/>
          </af:panelGroupLayout>
          <af:outputText value="Translate text, webpages and documents" id="ot2"
                         inlineStyle="font-size:medium; font-weight:bold;"/>
          <af:panelFormLayout id="pfl1" labelAlignment="top">
            <af:inputText id="it1" columns="100" rows="6"
                          label="Enter text or a webpage URL, or upload a document."
                          value="#{backingBeanScope.googleTranslate.input}"
                          autoSubmit="true" partialTriggers="cb2"/>
            <f:facet name="footer">
              <af:panelGroupLayout id="pgl3">
                <af:panelGroupLayout id="pgl5" layout="horizontal">
                  <af:panelGroupLayout id="pgl6">
                    <af:panelFormLayout id="pfl2" labelAlignment="start">
                      <af:selectOneChoice label="Translate From" id="soc1"
                                          value="#{backingBeanScope.googleTranslate.translateFrom}"
                                          autoSubmit="true"
                                          converter="LanguageEnumConverter">
                        <f:selectItems value="#{backingBeanScope.googleTranslate.languageItems}"
                                       id="si1"/>
                      </af:selectOneChoice>
                      <af:selectOneChoice label="Translate To" id="soc2"
                                          value="#{backingBeanScope.googleTranslate.translateTo}"
                                          autoSubmit="true"
                                          converter="LanguageEnumConverter">
                        <f:selectItems value="#{backingBeanScope.googleTranslate.languageItems}"
                                       id="si2"/>
                      </af:selectOneChoice>
                    </af:panelFormLayout>
                    <f:facet name="separator"/>
                  </af:panelGroupLayout>
                  <af:panelGroupLayout id="pgl1">
                    <af:commandButton text="Translate" id="cb1"
                                      actionListener="#{backingBeanScope.googleTranslate.translate}"
                                      partialSubmit="true"/>
                    <af:commandButton text="Clear" id="cb2"
                                      actionListener="#{backingBeanScope.googleTranslate.clear}"/>
                    <f:facet name="separator">
                      <af:spacer width="10" height="10" id="s3"/>
                    </f:facet>
                  </af:panelGroupLayout>
                  <f:facet name="separator">
                    <af:spacer width="20" height="10" id="s1"/>
                  </f:facet>
                </af:panelGroupLayout>
                <af:panelGroupLayout id="pgl4" inlineStyle="width:800.0px;">
                  <af:separator id="s2"/>
                  <af:outputText value="#{backingBeanScope.googleTranslate.translated}"
                                 id="ot1" partialTriggers="cb1 cb2"/>
                </af:panelGroupLayout>
              </af:panelGroupLayout>
            </f:facet>
          </af:panelFormLayout>
        </af:panelGroupLayout>
      </af:form>
    </af:document>
  </f:view>
</jsp:root>

Create the GoogleTranslateForm class (the backing bean of translate.jspx)

The GoogleTranslateForm class is a backing bean that will hold the server side values of our components. In addition, it also provide selectItems composed of Google Translate API Language Enum.

ADF Faces RC greatly simplifies our development because of the partial page rendering (PPR) and auto submit features. As you can see in my code below, to implement the functionality, I don't even have a single value changed listener.
package soadev.blogspot.googletranslateapi.backing;

import com.google.api.translate.Language;
import com.google.api.translate.Translate;
import java.util.ArrayList;
import java.util.List;
import javax.faces.event.ActionEvent;
import javax.faces.model.SelectItem;

public class GoogleTranslateForm {
    private List <SelectItem> languageItems;
    private String input;
    private Language translateFrom = Language.ENGLISH;
    private Language translateTo = Language.DUTCH;
    private String translated;

    public void setLanguageItems(List<SelectItem> languages) {
        this.languageItems = languages;
    }

    public List<SelectItem> getLanguageItems() {
        if (languageItems == null){         
            languageItems = new ArrayList<SelectItem>();
            languageItems.add(new SelectItem(Language.ENGLISH, "ENGLISH"));
            languageItems.add(new SelectItem(Language.DANISH, "DANISH"));
            languageItems.add(new SelectItem(Language.DUTCH, "DUTCH"));
            languageItems.add(new SelectItem(Language.FILIPINO, "FILIPINO"));
            languageItems.add(new SelectItem(Language.NORWEGIAN, "NORWEGIAN"));
            languageItems.add(new SelectItem(Language.ARABIC, "ARABIC"));
            languageItems.add(new SelectItem(Language.CHINESE_SIMPLIFIED, "CHINESE_SIMPLIFIED"));
            languageItems.add(new SelectItem(Language.CHINESE_TRADITIONAL, "CHINESE_TRADITIONAL"));
            languageItems.add(new SelectItem(Language.FRENCH, "FRENCH"));
            languageItems.add(new SelectItem(Language.GERMAN, "GERMAN"));
            languageItems.add(new SelectItem(Language.GREEK, "GREEK"));
            languageItems.add(new SelectItem(Language.ITALIAN, "ITALIAN"));
            languageItems.add(new SelectItem(Language.JAPANESE, "JAPANESE"));
            languageItems.add(new SelectItem(Language.KOREAN, "KOREAN"));
            languageItems.add(new SelectItem(Language.RUSSIAN, "RUSSIAN"));
            languageItems.add(new SelectItem(Language.SPANISH, "SPANISH"));
        }
        return languageItems;
    }

    public void setInput(String input) {
        this.input = input;
    }

    public String getInput() {
        return input;
    }

    public void setTranslateFrom(Language translateFrom) {
        this.translateFrom = translateFrom;
    }

    public Language getTranslateFrom() {
        return translateFrom;
    }

    public void setTranslateTo(Language translateTo) {
        this.translateTo = translateTo;
    }

    public Language getTranslateTo() {
        return translateTo;
    }

    public void setTranslated(String translated) {
        this.translated = translated;
    }

    public String getTranslated() {
        return translated;
    }

    public void translate(ActionEvent actionEvent) {
        
       try{
            translate();
        }
        catch (Exception e) {
            // TODO: Add catch code
            e.printStackTrace();
        }
    }
    public void translate() throws Exception{           
            if(input != null){
                //below are proxy related configuration
//                System.setProperty("http.proxyHost", "yourHost");             
//                System.setProperty("http.proxyPort", "8080");
                Translate.setHttpReferrer("http://localhost");
                translated = Translate.execute(input, getTranslateFrom(), getTranslateTo());
                
            }
    }

    public void clear(ActionEvent actionEvent) {
        input = null;
        translated = null;
    }
}

Create and register a LanguageEnumConverter class

Below is my LanguageEnumConverter class:
package soadev.blogspot.googletranslateapi.convert;

import com.google.api.translate.Language;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

public class LanguageEnumConverter implements Converter {
   
    @Override
    public Object getAsObject(FacesContext facesContext,
                              UIComponent uIComponent, String string) {
        return Language.fromString(string);
    }
    @Override
    public String getAsString(FacesContext facesContext,
                              UIComponent uiComponent, Object object) {
        return object.toString();
    }
}
We need to register this converter in faces-config.xml.
<?xml version="1.0" encoding="UTF-8"?>
<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>
  </application>
  <converter>
    <converter-id>LanguageEnumConverter</converter-id>
    <converter-class>soadev.blogspot.googletranslateapi.convert.LanguageEnumConverter</converter-class>
  </converter>
</faces-config>
This converter is used in the translate.jspx above.


Conclusion

With minimal effort, we can leverage the valuable service offered by Google Translate.
Cheers!

ps. If your testing behind a firewall that requires proxy authentication, read the following post.

Related Posts

Wednesday, February 24, 2010

ADF Faces RC: Building a Reusable Google Map Viewer that Supports both Geocoding and Reverse Geocoding (Applying Frank Nimphius' Declarative Lightweight Popup)

In this post, I will show you how to build a reusable Google Map Viewer that supports both Geocoding and Reverse Geocoding. This map viewer is implemented based on Frank Nimphius' declarative lightweight popup pattern. This can be incorporated from anywhere in your application that has address information.

Our steps to recreate this would be as follows:
  1. Sign up for the Google Maps API
  2. Create a new Task Flow named "google-map-viewer-task-flow" with a single view and a task-flow-return activity.
  3. Define task-flow input and return parameters
  4. Create and design the google_map.jspx
  5. Copy my javascript code
  6. Create the GoogleMapViewerForm (the backing bean of google_map.jspx)
  7. Integrate the google-map-viewer-task-flow as a task-flow-call activity into your existing task flows

Sign up for the Google Maps API

Go to the following site: http://code.google.com/apis/maps/signup.html and secure an API Key. A single Maps API key is valid for a single directory or domain. If you are testing your app using http://localhost:7101 or http://127.0.0.1:7101, then you should input that in the "My web site URL" textbox when generating the key. You must have a Google Account to get a Maps API key, and your API key will be connected to your Google Account. Keep the API Key for later use.

Create a new Task Flow named "google-map-viewer-task-flow" with a single view and a task-flow-return activity

Create the task flow with a single view named "google_map", a task-flow-return named "exit", and a control-flow-case named "return" as depicted below:

Define taskflow input and return parameters

Below are the optional input parameters and there definitions:
  • address - if a valid latitude or longitude parameter is not available, the map viewer will try to geocode the address;
  • latitude - both latitude and longitude should be provided so that the map viewer will process the coordinates and center it on map, otherwise the map viewer will just geocode for the address;
  • longitude - same as latitude info above;
  • countryCode - to prefer results to this country (but not to restrict the results); stated as a two-letter ISO compliant code;
Below are the defined output parameters which could return null values if no point was clicked on the map for reverse geocoding:
  • returnAddress - reverse geocode resulting address;
  • returnLatitude - selected point latitude;
  • returnLongitude - selected point longitude, selected coordinates are mark with an arrow marker;
  • returnDetails - if a point was selected in the map for reverse geocoding, the selected coordinates and the resulting details (all information from the client including the address above) will be returned here, otherwise null;

Create and design the google_map.jspx

Double click the google_map view in the task flow to invoke the "create page" wizard. You could put the page under ".../public_html/pages". Basically here are the items that we need to put on our page:
  • a clientListener that upon page load invokes the initialize javascript which inturn call a serverListener;
  • a serverListener mentioned above that invokes a server side code;
  • a <af:resource> component that will import our external javascript;
  • a panelHeader where the map will be placed;
  • an inputTextBox to input address to search.
  • a button to invoke search;
  • a button to return to the calling task-flow;
<?xml version="1.0" encoding="UTF-8"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
          xmlns:v="urn:schemas-microsoft-com:vml">
  <jsp:directive.page contentType="text/html;charset=UTF-8"/>
  <f:view>
    <af:document id="d1" clientComponent="true">
        <af:clientListener method="initialize" type="load" />
        <af:serverListener type="loadGoogleMap"
                           method="#{backingBeanScope.googleMapViewer.loadGoogleMap}" />
        <af:serverListener type="submitInfoToServer"
                           method="#{backingBeanScope.googleMapViewer.setReturnValues}" />
      
      <af:form id="f1" defaultCommand="cb1">
       
        <af:panelStretchLayout id="psl1" topHeight="auto">
          <f:facet name="center">
            <af:panelHeader id="mapPH" text=" "
                            inlineStyle="width:700PX; height: 400px">
              <f:facet name="toolbar"/>
            </af:panelHeader>
          </f:facet>
          <f:facet name="top">
            <af:panelBorderLayout id="pbl1">
              <f:facet name="start">
                <af:panelGroupLayout id="pgl2" layout="horizontal">
                  <af:inputText id="searchField" clientComponent="true"
                                columns="100"/>
                  <af:commandButton id="cb1" text="Search"
                                    clientComponent="true" partialSubmit="true">
                    <af:clientListener type="click" method="goFindAddress"/>
                  </af:commandButton>
                </af:panelGroupLayout>
              </f:facet>
              <f:facet name="end">
                <af:panelGroupLayout id="pgl1">
                  <af:commandButton text="Return" id="cb2" action="return"/>
                </af:panelGroupLayout>
              </f:facet>
            </af:panelBorderLayout>
          </f:facet>
        </af:panelStretchLayout>
      </af:form>
      <f:facet name="metaContainer">
        <af:group>
          <af:resource type="javascript" source="http://www.google.com/jsapi?key=YOUR_KEY_FROM_STEP_1"></af:resource>      
           <af:resource type="javascript">
    google.load("maps", "2.x");
   </af:resource>
   <af:resource type="javascript" source="/js/google_map_viewer.js"/>
   <af:resource type="javascript">
          window.onunload = GUnload;
          </af:resource>      
         </af:group>
      </f:facet>
    </af:document>
  </f:view>
</jsp:root>
Find the snippet below in the above code and replace the key with the key you generated in step 1:
<af:resource type="javascript" source="http://www.google.com/jsapi?key=YOUR_KEY_FROM_STEP_1"></af:resource>   

Copy my javascript code

This the bonus part. You'll just have to copy my code below for now and understand it later. Put the google_map_viewer.js under ".../public_html/js" folder.
/**http://soadev.blogspot.com/
*/

var map;
var geocoder;
var orig_coordinates;
var orig_marker;

function initialize(event) {
    var source = event.getSource();
    AdfCustomEvent.queue(source, "loadGoogleMap", 
    {
    },
false);
}

function initializeMap(clientId, latitude, longitude, address, countryCode) {
    if (GBrowserIsCompatible()) {
        map = new google.maps.Map2(document.getElementById(clientId));
        map.addControl(new GLargeMapControl());
        map.addControl(new GMapTypeControl());
        GEvent.addListener(map, 'click', getAddress);
        geocoder = new GClientGeocoder();
        //tailor to a particular domain (country)     
        if (countryCode) {
            geocoder.setBaseCountryCode(countryCode);
        }
        if (latitude != null && longitude != null) {
            //valid coordinates
            //set center of map to the coordinates and add a marker 
            orig_coordinates = new GLatLng(latitude, longitude);
            map.setCenter(orig_coordinates, 15);
            orig_marker = new GMarker(orig_coordinates);
            map.addOverlay(orig_marker);
            var info = "Original Coordinates: " + latitude + "," + longitude;
            addClickListener(orig_marker, info);
        }
        else {
            //none valid coordinates so try to find the address instead
            if (address != null) {
                findAddress(address);
            }
            else {
                //address is null so set center to default
                map.setCenter(new GLatLng(0, 0), 1);
            }
        }
    }
}

function getAddress(overlay, latlng) {
    //if far enough return
    if (map.getZoom() < 14) {
        return;
    }
    if (latlng != null) {
        var icon = createArrowIcon();
        arrow_marker = new GMarker(latlng, 
        {
            icon : icon
        });
        map.clearOverlays();
        map.addOverlay(arrow_marker);
        addClickListener(arrow_marker, latlng.toUrlValue());
        if (orig_marker) {
            map.addOverlay(orig_marker);
            var info = "Original Coordinates: " + orig_coordinates.toUrlValue();
            addClickListener(orig_marker, info);
        }
        //Reverse GeoCode
        geocoder.getLocations(latlng, showAddress);
    }
}

function createArrowIcon() {
    var icon = new GIcon();
    var url = "http://maps.google.com/mapfiles/";
    icon.image = url + "arrow.png";
    icon.shadow = url + "arrowshadow.png";
    icon.iconSize = new GSize(39, 34);
    icon.shadowSize = new GSize(39, 34);
    icon.iconAnchor = new GPoint(20, 34);
    icon.infoWindowAnchor = new GPoint(20, 0);
    return icon;
}

function showAddress(response) {
    if (!response || response.Status.code != 200) {
        alert("Status Code:" + response.Status.code);
    }
    else {
        submitInfoToServer(response);
        place = response.Placemark[0];
        plotPlace(place);
    }
}

function goFindAddress(event) {
    var searchField = event.getSource().findComponent("searchField");
    var input = searchField.getValue();
    findAddress(input);
    event.cancel();
}

function findAddress(input) {
    var address = input;
    //GeoCode
    if (geocoder) {
        geocoder.getLocations(input, function (response) {
            if (!response || response.Status.code != 200) {
                var index = address.indexOf(",");
                if (index ==  - 1) {
                    processPointNotFound();
                    return;
                }
                //simplify address by removing the details separated by comma
                address = address.substring(index + 1);
                //recurse until a point is found
                findAddress(address);
            }
            else {
                place = response.Placemark[0];
                plotPlace(place);
            }
        });
    }
}

function processPointNotFound() {
    alert("address not found");
    map.setCenter(new GLatlng(0, 0), 1);
}

function plotPlace(place) {
    var point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
    var marker = new GMarker(point);
    map.addOverlay(marker);
    map.setCenter(point, 14);
    var info = "<b>Result Coordinates:</b>" + point.toUrlValue() + "<br/>" + "<b>Address:</b>" + place.address + "<br/>" + "<b>Accuracy:</b>" + place.AddressDetails.Accuracy + "<br/>" + "<b>Country code:</b> " + place.AddressDetails.Country.CountryNameCode;
    addClickListener(marker, info);

}

function addClickListener(marker, info) {
    GEvent.addListener(marker, "click", function () {
        marker.openInfoWindowHtml(info);
    });
    marker.openInfoWindowHtml(info);
}

function submitInfoToServer(response) {
    var place = response.Placemark[0];
    var result_point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
    var address = place.address;
    var addressDetails = place.AddressDetails;
    var accuracy = addressDetails.Accuracy;
    var country = addressDetails.Country;
    var countryNameCode = country.CountryNameCode;
    var adminArea = country.AdministrativeArea;
    var adminAreaName;
    var locality;
    var localityName;
    var thoroughfare;
    var postalCode;
    if (adminArea != null) {
        adminAreaName = adminArea.AdministrativeAreaName;
        locality = adminArea.Locality;
        if (locality != null) {
            localityName = locality.LocalityName;
            thoroughfare = locality.Thoroughfare;
            postalCode = locality.PostalCode;
            if (thoroughfare != null) {
                thoroughfare = thoroughfare.ThoroughfareName;
            }
            if (postalCode != null) {
                postalCode = postalCode.PostalCodeNumber;
            }
        }
    }
    var source = AdfPage.PAGE.findComponent("d1");
    AdfCustomEvent.queue(source, "submitInfoToServer", 
    {
        selected_point : response.name, result_point:result_point , address : address, country : countryNameCode, area : adminAreaName, locality : localityName, thoroughfare : thoroughfare, postalCode : postalCode, accuracy : accuracy
    },
false);
}

Create the GoogleMapViewerForm (the backing bean of google_map.jspx)

Create a new class GoogleMapViewerForm and add a "googleMapViewer" managed bean with backingBean scope in google-map-viewer-task-flow.
package blogspot.soadev.view.backing;

import java.util.Map;
import javax.faces.context.FacesContext;
import oracle.adf.view.rich.context.AdfFacesContext;
import oracle.adf.view.rich.render.ClientEvent;
import org.apache.myfaces.trinidad.render.ExtendedRenderKitService;
import org.apache.myfaces.trinidad.util.Service;

/**@author pino
 */
public class GoogleMapViewerForm {

    public void loadGoogleMap(ClientEvent clientEvent){
        FacesContext context = FacesContext.getCurrentInstance();
        Map<String, Object> pageFlowScope = AdfFacesContext.getCurrentInstance().getPageFlowScope();
        Object obj = null;
        String address = null;
        String countryCode = null;
        Double latitude = null;
        Double longitude = null;
        
        address = (String)pageFlowScope.get("address");
        countryCode = (String) pageFlowScope.get("countryCode");
        obj = pageFlowScope.get("latitude");
        if (obj != null && obj instanceof Double){
            latitude = (Double)obj;
        }
        obj = pageFlowScope.get("longitude");
        if (obj != null && obj instanceof Double){
            longitude = (Double)obj;
        }
        //build javascript
        StringBuilder script = new StringBuilder();
        script
            .append("initializeMap(")
            .append("'mapPH',")
            .append(latitude)
            .append(",")
            .append(longitude)
            .append(",'")
            .append(address)
            .append("','")
            .append(countryCode)
            .append("');");
       
        ExtendedRenderKitService erks = 
            Service.getService(context.getRenderKit(), ExtendedRenderKitService.class);
          erks.addScript(context, script.toString());
    }

   public void setReturnValues(ClientEvent event) {
        Map<String, Object> pageFlowScope =AdfFacesContext.getCurrentInstance().getPageFlowScope();
        Map<String, Object> eventParams = event.getParameters();
        Object selectedCoordinates = eventParams.get("selected_point");
        if (selectedCoordinates != null){
            String coordinates[] = ((String)selectedCoordinates).split(",");
            Double latitude = Double.parseDouble(coordinates[0]);
            Double longitude = Double.parseDouble(coordinates[1]);
            pageFlowScope.put("returnLatitude", latitude);
            pageFlowScope.put("returnLongitude", longitude);
        }
        pageFlowScope.put("returnAddress", eventParams.get("address"));
        pageFlowScope.put("returnDetails", eventParams);
    }
}

Integrate the google-map-viewer-task-flow as a task-flow-call activity into your existing task flows

Steps to integrate the Google Map viewer into your application:
  1. Open your existing task flow in diagram view and drag a task-flow-call activity from the component pallete.
  2. Drag the google-map-viewer-task-flow from the project explorer into the task-flow-call activity created above.
  3. Add a control-flow-case and extend it from one of your page that has address information to our google-map-viewer-task-flow activity.
  4. Add a button on your page that will have an
A picture paints a thousand words. Please see screen shot below:
Screen shot of the sample page that invokes the map viewer:
The action listener is bound to the class SamplePageForm declared as "samplePage" managed bean with backingBean scope. Below is the illustrative code:
package soadev.blogspot.view.backing;

import java.util.Iterator;
import java.util.Map;
import javax.faces.event.ActionEvent;
import oracle.adf.view.rich.context.AdfFacesContext;
import org.apache.myfaces.trinidad.event.ReturnEvent;

public class SamplePageForm {
    public void setMapInputParams(ActionEvent event) {
        Map pageFlowScope = AdfFacesContext.getCurrentInstance().getPageFlowScope();
        //You will get the information to put to the pageFlowScope below
        //from your iterator binding objects. 
        //sample inputs only, replace Jeddah
        pageFlowScope.put("address", "Jeddah");
        //replace null with your own data;
        pageFlowScope.put("latitude", null);
        pageFlowScope.put("longitude", null);
        pageFlowScope.put("countryCode", null);
        
    }

    public void handleMapDialogReturn(ReturnEvent event) {
        System.out.println("handling dialog return...");
        Map eventReturnParams = event.getReturnParameters();
        Iterator iterator = eventReturnParams.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry)iterator.next();
            Object key = entry.getKey();
            Object value = entry.getValue();
            System.out.println(key + " ..... " + value);
        }
        Object obj = null;
        Double latitude = null;
        Double longitude = null;
        obj = eventReturnParams.get("latitude");
        if (obj != null && obj instanceof Double) {
            latitude = (Double)obj;
        }
        obj = eventReturnParams.get("longitude");
        if (obj != null && obj instanceof Double) {
            longitude = (Double)obj;
        }
        if (latitude != null && longitude != null) {
            //TODO create popup confirmation to apply new selected coordinates
            //showDialog(popup);
        }
    }
}
Below is a sample result of the handleMapDialogReturn() method above:
handling dialog return...
returnDetails ..... {postalCode=null, area=null, address=7901 Hail, Jeddah 23325, Saudi Arabia, locality=null, thoroughfare=null, selected_point=21.540356,39.160337, accuracy=8.0, country=SA, result_point={of=21.5404074, y=21.5404074, x=39.1601585, $a=39.1601585}}
returnLatitude ..... 21.540356
returnAddress ..... 7901 Hail, Jeddah 23325, Saudi Arabia
returnLongitude ..... 39.160337

Summary

This blogpost illustrate the following "How Tos":
  • How to integrate the Google Maps API to an Oracle ADF application.
  • How to run a backing bean code upon page load.
  • How to pass parameters from the server to the client and vice-versa.
  • How to do geocoding and reverse geocoding in Google Maps.
  • How to apply Frank Nimphius declarative lightweight popup pattern.

Related Posts

This post is long one (and it sure made me exhausted). I hope that you learn something from here.

Cheers!

Thursday, February 18, 2010

ADF UI Shell: Automatically Open a Default Activity Upon Page Load

I am watching the Oracle UI Shell Functional Pattern thread at OTN and noted that some people were interested on how to automatically open a default activity in ADF UI Shell. I decided to take up the challenge and came up with the following.

This post is just a demonstration on how to open a default activity upon page load in ADF UI Shell, based on the accompanying sample application named "UISHellSherman_V02".
Let us simulate the concept using the page First.jspx.
  1. Add the Trinidad HTML Components 1.2 JSP Tag Library on the uiShellViewController project
  2. Open First.jspx and add the Trinidad "trh" tag declaration on the jsp root. The jsp root declaration should look like the following:
    <?xml version='1.0' encoding='UTF-8'?>
    <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
              xmlns:f="http://java.sun.com/jsf/core"
              xmlns:h="http://java.sun.com/jsf/html"
              xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
              xmlns:trh="http://myfaces.apache.org/trinidad/html">
    
  3. Add the following client and server listeners:
    <af:document id="d1" title="First Page">
          <af:clientListener method="initialize" type="load" />
            <af:serverListener type="launchDefaultActivity"
                               method="#{backingBeanScope.launcher.launchDefaultActivity}" />
    

  4. Right-click the line <af:document id="d1" title="First Page"> click Facets -document, then enable the Meta Container facet.
    Modify the Meta Container facet to reflect the following:
    <f:facet name="metaContainer">
            <af:group>
             <trh:script>
                function initialize(event) {
                    var source = event.getSource();
                    AdfCustomEvent.queue(source,"launchDefaultActivity",{},false);
                }
              </trh:script>
            </af:group>
          </f:facet>
    
  5. Add a launchDefaultActivity() method on the Launcher class.
    public void launchDefaultActivity(ClientEvent clientEvent) {
            _launchActivity(
              "The Default Activity",
              "/WEB-INF/flows/first.xml#first",
              false);
        }
    
  6. Run the First.jspx

Related Posts


Cheers!

Monday, February 15, 2010

ADF Model: java.lang.NullPointerException at oracle.adf.model.binding.DCIteratorBinding.getSortCriteria

If you encountered this error though your code was running good before, then welcome to the club! :D
Here are some of our colleagues:
ADF webapp deployment problem...
JDBC connection for ADF
Both of them have resolve their case by recreating their application and build it from scratch. Yaiks! We sure cannot afford that on an app that we build for months.

Luckily, I was able to resolve my case by doing the steps below, but in your case, I don't know, so good luck!

It could be resolved by doing the following:
  1. Open the page definition of the target page that cause this error
  2. Find the method action that supports the page like ("findEmployeeById")
  3. Copy it to notepad for later reference
  4. Delete it. delete it even though that it is properly defined
  5. Recreate the method action above (i did it thru the overview tab)
  6. Run

Did it work?
Can somebody explain this for me?!!

Cheers!

EJB with EclipseLink: How to Synchronize to the Database the Objects that were Removed from a List Attribute while the Entity is in Detached State

When developing web applications, it is likely that we will deal with entity objects in detached state (unmanaged state) while working with them in the web layer.
In my case, I have an AcctgEntry object that has a list of AcctgLine objects as an attribute.
public class AcctgEntry{

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "GL_TRANS_HEADER_SEQ_GEN")
    @Column(name = "GL_TRANS_HEADER_ID", nullable = false)
    private Long id;

 
    @OneToMany(mappedBy = "acctgEntry", cascade = CascadeType.ALL)
    private List<AcctgLine> acctgLineList = new ArrayList<AcctgLine>();
    
    //plus other attributes
    //plus getters and setters
    //plus other relevant methods

}
public class AcctgLine{
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "GL_TRANS_DETAIL_SEQ_GEN")
    @Column(name = "GL_TRANS_DETAIL_ID", nullable = false)
    private Long id;

    @Column(name = "LINE_NO", nullable = false)
    private Long lineNo;
    
    @ManyToOne
    @JoinColumn(name = "GL_NATURAL_ACCT_ID", nullable = false)
    private NaturalAcct naturalAcct;

    @ManyToOne
    @JoinColumn(name = "GL_TRANS_HEADER_ID")
    private AcctgEntry acctgEntry;

    //plus other attributes
    //plus getters and setters
    //plus other relevant methods

}

AcctgEntry objects can be created, saved and can be edited multiple times as long as the entry is not yet posted. My web interface is bound to a model which is queried from a stateless session bean. In this scenario, my model object is in detached state.
Sample case:
A user created an entry with the following AcctgLine objects:
acctgEntry JV#001
    -acctgLine1  DR100  
    -acctgLine2         CR100
When the user invoked Save, my service class verify if the Debit and Credit are balanced, and indeed it is, so the the entry was persisted to the database.
Now the problem is this. The user edited the acctgEntry, he removed the -acctgLine2 and and added two additional acctgLines as follows:
acctgEntry JV#001
    -acctgLine1  DR100
    -acctgLine3         CR50
    -acctgLine4         CR50
The user again invoked Save and my service class verify if the entry is balanced and and again succeeded, so the entry was merged to the database. But when I requery the acctgEntry that was updated, it has now as follows:
acctgEntry JV#001
    -acctgLine1  DR100
    -acctgLine2         CR100
    -acctgLine3         CR50
    -acctgLine4         CR50
I was puzzled because the -acctgLine2 was not removed from the database, and now my entry is imbalanced (Total debits 100, but the totals credits is 200).

I check on the internet and noted that this is a valid behavior based on the jpa specification. You can read the discussion here: JPA OneToMany not deleting child

With further research, I am happy to solved my issue with just a single additional persistence annotation- @PrivateOwned. More about this annotation here: How to Use the @PrivateOwned Annotation. With this annotation, whenever acctgLines were removed from the AcctgLine list attribute on my AcctgEntry object, the changes will be synchronized to the database upon merge.
My AcctgEntry class is now something like below:
public class AcctgEntry{

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "GL_TRANS_HEADER_SEQ_GEN")
    @Column(name = "GL_TRANS_HEADER_ID", nullable = false)
    private Long id;

    @PrivateOwned
    @OneToMany(mappedBy = "acctgEntry", cascade = CascadeType.ALL)
    private List<AcctgLine> acctgLineList = new ArrayList<AcctgLine>();
    
    //plus other attributes
    //plus getters and setters
    //plus other relevant methods

}

Cheers!

Sunday, February 14, 2010

ADF UI Shell: Dynamic Tree Menu based on User Roles (ADF Policies)

In this post, I will try to share with you how to create a dynamic tree menu based on the authorization defined in ADF Policies (jazn.xml) in an application built with ADF UI Shell. Menu Items will be added to the tree menu, if the user has a role that was granted view authorization on a certain task-flow or page.


To accomplish the creation of a dynamic menu based on user roles, we need to do at least the following:
  1. Create and populate a Menu Table in the database;
  2. Generate a Menu entity thru the "Entities from Table" wizard in JDeveloper;
  3. Create a session bean to act as facade of our entity;
  4. Create a managed bean to provide tree model of authorized menus in appropriate hierarchy;
  5. Create a .jspx page based on the Oracle Dynamic Tabs Shell Template.
  6. Create a Launcher backing bean that will support our page and provide method to launch new tabs.
  7. Grant view authorization to our taskflows defined in jazn.xml to certain roles.

1) Create and populate a Menu Table in the database;

To support a dynamic menu based on roles we only need to have a table of menu items. A table of roles is not necessary. The key to the solution is the java version of the "#{securityContext.taskFlowViewable['taskFlowId']}" EL expression. Below is the diagram of our table:
ColumnComments
MENU_ID Primary key
DESCRIPTIONThe label that will be displayed on the tree.
DEFINITIONThis will hold taskFlowIds like for example "/WEB-INF/taskflows/gl/natural-acct-list-task-flow.xml#natural-acct-list-task-flow".
PARENT_MENU_IDThe id of the containing menu. This table is recursive.
TYPEThis field will enable us to show different icons per type.
DISPLAY_SEQThe sorting or display sequence of the menus.
IS_MULTIPLE_INSTANCERepresents a boolean value to determine if multiple instance of this taskflow will be allowed on UI Shell tabs.

2) Generate a Menu entity thru the "Entities from Table" wizard in JDeveloper;

Below is a sample generated Menu Entity class (please note the annotations are on the properties instead of the fields - I encountered strange error in related to jpa sessions ("this session is not of the current object but of the parent blah blah...")when I attempted to put the annotations in the fields ).
package blogspot.soadev.model;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;

@Entity
@NamedQueries({
  @NamedQuery(name = "findAllMenus", query = "select o from Menu o"),
  @NamedQuery(name = "findRootMenus", query = "select o from Menu o where o.parentMenu IS NULL"),
  @NamedQuery(name = "findTargetRootMenu", query = "select o from Menu o where o.id = :menuId")
})
public class Menu implements Comparable<Menu>, Serializable {   
    private Long id;
    private String description;  
    private String definition;   
    private String type;   
    private Menu parentMenu;   
    private Long displaySeq;  
    private boolean multipleInstance;   
    private List<Menu> childrenMenuList;  
 
    @Column(name="DISPLAY_SEQ", nullable = false)
    public Long getDisplaySeq() {
        return displaySeq;
    }

    public void setDisplaySeq(Long displaySeq) {
        this.displaySeq = displaySeq;
    }
    @Id
    @Column(name="MENU_ID", nullable = false)
    public Long getId() {
        return id;
    }

    public void setId(Long menuId) {
        this.id = menuId;
    }
    @Column(length = 120)
    public String getDescription() {
        return description;
    }

    public void setDescription(String name) {
        this.description = name;
    }
    @Column(length = 120)
    public String getDefinition() {
        return definition;
    }

    public void setDefinition(String taskFlowId) {
        this.definition = taskFlowId;
    }
    @Column(length = 1)
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
    @ManyToOne
    @JoinColumn(name = "PARENT_MENU_ID")
    public Menu getParentMenu() {
        return parentMenu;
    }

    public void setParentMenu(Menu menu) {
        this.parentMenu = menu;
    }
    
    public void setMultipleInstance(boolean multipleInstance) {
        this.multipleInstance = multipleInstance;
    }
    @Column(name ="IS_MULTIPLE_INSTANCE", length = 1)
    public boolean isMultipleInstance() {
        return multipleInstance;
    }
    @OneToMany(mappedBy = "parentMenu")
    public List<Menu> getChildrenMenuList() {
        Collections.sort(childrenMenuList);
        return childrenMenuList;
    }

    public void setChildrenMenuList(List<Menu> menuList) {
        this.childrenMenuList = menuList;
    }

    public Menu addMenu(Menu menu) {
        getChildrenMenuList().add(menu);
        menu.setParentMenu(this);
        return menu;
    }

    public Menu removeMenu(Menu menu) {
        getChildrenMenuList().remove(menu);
        menu.setParentMenu(null);
        return menu;
    }

    public int compareTo(Menu m) {
        if (m == null){
            return -1;
        }
        return this.displaySeq.compareTo(m.displaySeq);
    }
}
You could note above that I added two named queries: findRootMenus and findTargetMenu. I also implemented a Comparable interface to support sorting of menus based on the desired display sequence.

3) Create a session bean to act as facade of our entity;

Below is my generated session bean:
package blogspot.soadev.service;

import java.util.List;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import blogspot.soadev.model.Menu;

@Stateless(name = "ApplicationManager", mappedName = "DynamicTreeMenuBasedOnRoles-Model-ApplicationManager")
@Remote
@Local
public class ApplicationManagerBean implements ApplicationManager,
                                               ApplicationManagerLocal {
    @PersistenceContext(unitName="Model")
    private EntityManager em;


    /** <code>select o from Menu o</code> */
    public List<Menu> findAllMenus() {
        return em.createNamedQuery("findAllMenus").getResultList();
    }

    /** <code>select o from Menu o where o.id = menuId</code> */
    //I intend it to return a list instead of just a menu.
    public List<Menu> findTargetRootMenu(Long menuId) {
        return em.createNamedQuery("findTargetRootMenu").setParameter("menuId", menuId).getResultList();
    }

    /** <code>select o from Menu o where o.parentMenu IS NULL</code> */
    public List<Menu> findRootMenus() {
        return em.createNamedQuery("findRootMenus").getResultList();
    }
}

4) Create a managed bean to provide tree model of authorized menus in appropriate hierarchy;

Below is the managed bean that we will declare in adfc-config.xml as view scope. This will supply a tree model to our tree component:
package blogspot.soadev.view.managed;

import blogspot.soadev.model.Menu;
import blogspot.soadev.view.util.JSFUtils;
import java.beans.IntrospectionException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import oracle.adf.controller.security.TaskFlowPermission;
import oracle.adf.share.ADFContext;
import oracle.adf.share.security.SecurityContext;
import oracle.adf.share.security.authorization.RegionPermission;
import oracle.binding.BindingContainer;
import oracle.binding.OperationBinding;
import org.apache.myfaces.trinidad.model.ChildPropertyTreeModel;
import org.apache.myfaces.trinidad.model.TreeModel;

public class TreeMenuNavigator implements Serializable {
    private TreeModel model;
    private List<Menu> menuList;
    private List<Menu> authorizedMenuList = new ArrayList<Menu>();
    private Long rootMenuId = 2L; //the root menu Id of my default page
    private Map<Long, Menu> menuMap;

    public void setModel(TreeModel model) {
        this.model = model;
    }

    public TreeModel getModel() throws IntrospectionException {
        if (model == null) {
            model =
                    new ChildPropertyTreeModel(getMenuList(), "childrenMenuList");
        }
        return model;
    }

    public void setMenuList(List<Menu> menuList) {
        this.menuList = menuList;
    }

    public List<Menu> getMenuList() {
        BindingContainer bindings =
            (BindingContainer)JSFUtils.resolveExpression("#{bindings}");
        //I used the findTargetRootMenu() method instead of the findRootMenus
        //because I am reusing the same model for menu of other global tabs as well.
        //A global tab will have a different menu based on the root menu id 
        //that I set on this class whenever I invoke a global tab.
        //for testing purposes you could use the findRootMenus()
        //Please ensure to add the appropriate methodAction in the page definition of the containing .jspx page
        OperationBinding oper =
            (OperationBinding)bindings.getOperationBinding("findTargetRootMenu");
        List<Menu> rootMenuList = (List<Menu>)oper.execute();
        //initialize attributes
        authorizedMenuList = new ArrayList<Menu>();
        menuMap = new HashMap<Long, Menu>();
        menuList = new ArrayList<Menu>();
        reinitializeAuthorizedMenuList(rootMenuList);
        reconstructHierarchicalMenuList(authorizedMenuList);
        List<Menu> resultList = menuList;
        //release attributes that was used in recursive methods
        authorizedMenuList = null;
        menuMap = null;
        menuList = null;
        if (!resultList.isEmpty()) {
            return resultList.get(0).getChildrenMenuList();// I prefer to return the immediate children 
             //rather than the root. But you could try returning resultList instead
            //return resultList;
        }
        return Collections.emptyList();
    }

    private void reinitializeAuthorizedMenuList(List<Menu> menuList) {
        if (menuList == null) {
            return;
        }
        for (Menu menu : menuList) {
            if (isAccessible(menu.getDefinition())) {
                authorizedMenuList.add(menu);
            } else {
                reinitializeAuthorizedMenuList(menu.getChildrenMenuList());
            }
        }
    }


    public void reconstructHierarchy(Menu menu) {
        if (menu == null) {
            return;
        }
        if (menuMap.containsKey(menu.getId())) { //menu already loaded
            return;
        }
        menuMap.put(menu.getId(), menu);
        Menu m = menu.getParentMenu();
        if (m == null) {
            menuList.add(menu);
            return;
        }
        Menu parentCopy = null;
        if (menuMap.containsKey(m.getId())) {
            parentCopy = menuMap.get(m.getId());
        } else {
            parentCopy = copyAttributes(m);
            reconstructHierarchy(parentCopy);
        }
        parentCopy.addMenu(menu);
    }


    public void reconstructHierarchicalMenuList(List<Menu> authorizedMenuList) {
        for (Menu menu : authorizedMenuList) {
            Menu copy = copyAttributes(menu);
            reconstructHierarchy(copy);
        }
    }

    private Menu copyAttributes(Menu menu) {
        if (menu == null) {
            return null;
        }
        Menu copy = new Menu();
        copy.setId(menu.getId());
        copy.setDescription(menu.getDescription());
        copy.setDefinition(menu.getDefinition());
        copy.setParentMenu(menu.getParentMenu());
        copy.setDisplaySeq(menu.getDisplaySeq());
        copy.setType(menu.getType());
        copy.setMultipleInstance(menu.isMultipleInstance());
        return copy;
    }

    //this is the Java version of the "#{securityContext.regionViewable['pageDef']}" EL Expression
    public boolean isRegionViewable(String pageDef) {
        if (pageDef == null) {
            return false;
        }
        RegionPermission permission =
            new RegionPermission(pageDef, RegionPermission.VIEW_ACTION);
        SecurityContext ctx = ADFContext.getCurrent().getSecurityContext();
        return ctx.hasPermission(permission);
    }
    //this is the Java version of the "#{securityContext.taskFlowViewable['taskFlowId']}" EL Expression
    public boolean isTaskFlowViewable(String taskflowId) {
        if (taskflowId == null) {
            return false;
        }
        TaskFlowPermission permission =
            new TaskFlowPermission(taskflowId, TaskFlowPermission.VIEW_ACTION);
        SecurityContext ctx = ADFContext.getCurrent().getSecurityContext();
        return ctx.hasPermission(permission);
    }

    public boolean isAccessible(String definition) {
        return (isRegionViewable(definition) ||
                isTaskFlowViewable(definition));
    }

    public void setRootMenuId(Long rootMenuId) {
        this.rootMenuId = rootMenuId;
        model = new ChildPropertyTreeModel(getMenuList(), "childrenMenuList");
    }

    public Long getRootMenuId() {
        return rootMenuId;
    }
}

5) Create a .jspx page based on the Oracle Dynamic Tabs Shell Template.

Below is a snippet of my jspx page based on an extended UI Shell that shows the source of our tree component:
<af:tree var="node" rowSelection="single" id="menuTree"
                         value="#{viewScope.treeMenuNavigator.model}"
                         initiallyExpanded="true" fetchSize="-1"
                         contentDelivery="immediate">
                  <f:facet name="nodeStamp">
                    <af:panelGroupLayout id="pgl10">
                      <af:outputText value="#{node.description}" id="ot2"
                                     rendered="#{node.definition eq null}"
                                     inlineStyle="font-size:larger; font-weight:bold;"/>
                      <af:commandImageLink text="#{node['description']}" id="pt_cil5"
                                           rendered="#{node.definition  ne null}"
                                           icon="#{node.type eq 'L' ? '/images/List16.png': node.type eq 'C' ? '/images/Maintain16.png': node.type eq 'P' ? '/images/Process16.png': node.type eq 'R' ? '/images/Report16.png':  '/images/Transaction16.png'}"
                                           actionListener="#{backingBeanScope.launcher.launchMenu}"
                                           partialSubmit="true"
                                           immediate="true">
                        <f:attribute name="node" value="#{node}"/>
                      </af:commandImageLink>
                    </af:panelGroupLayout>
                  </f:facet>
                </af:tree>

6) Create a Launcher backing bean that will support our page and provide method to launch new tabs.

Below is our Launcher class declared in backingBean scope in adfc-config.xml:
package blogspot.soadev.view.backing;

import blogspot.soadev.model.Menu;
import javax.faces.component.UIComponent;
import javax.faces.event.ActionEvent;
import oracle.ui.pattern.dynamicShell.TabContext;

public class Launcher {
    
    public void launchMenu(ActionEvent event) {
        UIComponent component = (UIComponent)event.getSource();
        Menu menu = (Menu)component.getAttributes().get("node");
        _launchActivity(menu.getDescription(), menu.getDefinition(),
                       menu.isMultipleInstance());
    }
    private void _launchActivity(String title, String taskflowId, boolean newTab)
    {
      try
      {
        if (newTab)
        {
          TabContext.getCurrentInstance().addTab(
            title,
            taskflowId);
        }
        else
        {
          TabContext.getCurrentInstance().addOrSelectTab(
            title,
            taskflowId);
        }
      }
      catch (TabContext.TabOverflowException toe)
      {
        // causes a dialog to be displayed to the user saying that there are
        // too many tabs open - the new tab will not be opened...
        toe.handleDefault(); 
      }
    }
}
Whew! This is rather a long post. To be continued... Continuation...

7) Grant view authorization to our taskflows defined in jazn.xml to certain roles.

Please refer to this post by Chris Muir for detailed info about the minimum setting to run UI Shell with ADF Security. Be sure to check my comments on the bottom :)

Cheers!

Wednesday, February 10, 2010

Java: Comparing BigDecimal objects for equality

If you are developing a financial application you would probably agree that 1.0 is equivalent to 1.00, but the BigDecimal API says otherwise. If you will run the following code, you will be bewildered at first on the results:
package blogspot.soadev.test;
import java.math.BigDecimal;
public class BigDecimalTestRunner {
    public static void main(String[] args) {
        BigDecimal x = new BigDecimal("1.0");
        BigDecimal y = new BigDecimal("1.00");
        System.out.println(x.equals(y));
        System.out.println(x.compareTo(y) == 0);
    }
}
The result:
false
true
Some excerpts from the BigDecimal JavaDocs:

equals
public boolean equals(Object x)
Compares this BigDecimal with the specified Object for equality. Unlike compareTo, this method considers two BigDecimals equal only if they are equal in value and scale (thus 2.0 is not equal to 2.00 when compared by this method).
Overrides:
equals in class Object
Parameters:
x - Object to which this BigDecimal is to be compared.
Returns:
true if and only if the specified Object is a BigDecimal whose value and scale are equal to this BigDecimal's.

compareTo
public int compareTo(BigDecimal val)
Compares this BigDecimal with the specified BigDecimal. Two BigDecimals that are equal in value but have a different scale (like 2.0 and 2.00) are considered equal by this method. This method is provided in preference to individual methods for each of the six boolean comparison operators (<, ==, >, >=, !=, <=). The suggested idiom for performing these comparisons is: (x.compareTo(y) <op> 0), where <op> is one of the six comparison operators.
Parameters:
val - BigDecimal to which this BigDecimal is to be compared.
Returns:
-1, 0 or 1 as this BigDecimal is numerically less than, equal to, or greater than val.

So therefore, the next time you will check the BigDecimal objects for equality in the financial context, use compareTo() instead of equals().

Tuesday, February 9, 2010

ADF Faces RC: A simple but robust implementation of a conditional confirmation dialog/ pop-up

Sometimes you may need to display a confirmation dialog in response to a user action. You can easily launched a pop-up by adding a popup and a showPopupBehavior component inside your commandButton, commandToolbarButton, and etc. like below:
<af:commandToolbarButton text="Post" id="ctb4">
  <af:showPopupBehavior popupId="confirmPostingPopup"
                           triggerType="action"/>
</af:commandToolbarButton>

But in case your requirement is to conditionally display a pop-up dialog based on some premise, then you need to programmatically launch your pop-up from your backing bean. The following method could make this daunting task a breeze:
protected void showDialog(RichPopup popup){
      FacesContext context = FacesContext.getCurrentInstance();
      StringBuilder script = new StringBuilder();
      script
        .append("var popup = AdfPage.PAGE.findComponent('")
        .append(popup.getClientId(context))
        .append("'); ")
        .append("if (!popup.isPopupVisible()) { ")
        .append("var hints = {}; ") 
        .append("popup.show(hints);}");
      ExtendedRenderKitService erks = 
          Service.getService(context.getRenderKit(), ExtendedRenderKitService.class);
        erks.addScript(context, script.toString());
    }   
Below is a sample popup declared on one of my page.
<af:popup id="confirmPostingPopup" clientComponent="true" contentDelivery="lazy"
            binding="#{backingBeanScope.acctgEntryEditForm.confirmPostingPopup}">
    <af:dialog title="Confirm Posting" type="yesNo" id="pt_d2"
               dialogListener="#{backingBeanScope.acctgEntryEditForm.handleConfirmPostingDialog}">
      <af:panelGroupLayout id="pgl0" layout="vertical">
        <af:outputText value="Posted entries can no longer be edited."
                       id="pt_ot4"/>
        <af:outputText value="Are you sure to post this entry? " id="pt_ot3"/>
      </af:panelGroupLayout>
    </af:dialog>
  </af:popup>
The button that could potentially launched that popup...
<af:commandToolbarButton text="Post" id="ctb4" actionListener="#{backingBeanScope.acctgEntryEditForm.postEntry}"/>
You need to take note of three points on the above codes:
1) The popup has a "binding" attribute to bound it to your backing bean so that you could pass it later as parameter to your showDialog()method.
2) The enclosing dialog inside the popup has a declared "dialogListener" attribute. The listener here will take charge on what to do based on the user response(Yes or No).
3) The commandToolbarButton has no declared showPopupBehavior tag but an actionListener. Inside the actionListener method is the decision to call the showDialog() method.
Please see illustrative codes below:
public class AcctgEntryEditForm{
    private RichPopup confirmPostingPopup;
    //getter and setter
    public void setConfirmPostingPopup(RichPopup confirmPostingPopup){
        this.confirmPostingPopup = confirmPostingPopup;
    }

    public RichPopup getConfirmPostingPopup() {
        return confirmPostingPopup;
    }
    //actionListener
    public void postEntry(ActionEvent event) {
        // some codes
        if (true) {//subtitute true with your own criteria
            showDialog(confirmPostingPopup);
        }
    }
    //dialogListener
    public void handleConfirmPostingDialog(DialogEvent ev){
      if (ev.getOutcome().equals(DialogEvent.Outcome.yes)){
         //the user confirms, do your stuff here...
         System.out.println("The user confirmed");
      }
    }
    
    protected void showDialog(RichPopup popup){
      FacesContext context = FacesContext.getCurrentInstance();
      StringBuilder script = new StringBuilder();
      script
        .append("var popup = AdfPage.PAGE.findComponent('")
        .append(popup.getClientId(context))
        .append("'); ")
        .append("if (!popup.isPopupVisible()) { ")
        .append("var hints = {}; ") 
        .append("popup.show(hints);}");
      ExtendedRenderKitService erks = 
          Service.getService(context.getRenderKit(), ExtendedRenderKitService.class);
        erks.addScript(context, script.toString());
    }
}

Cheers!

Monday, February 8, 2010

The ADF Faces RC: <af:resetActionListener/> tag works like magic!

I can't imagine that my half-day of debugging on how to reset the input components in my form to original value will be solved by a single tag, and that is:
<af:resetActionListener/>

Below is an excerpt from the JDeveloper help:
The resetActionListener tag is a declarative way to allow an action source (<commandbutton>, <commandlink>, etc.) to fire a reset action. All values submitted will be reset to null or empty. The reset will not alter any model state directly, rather the enclosing action should have its own actionListener which will reset all model values to their defaults. The resetActionListener tag supports no attributes.
Example:
This example shows a "Reset..." button. When the button is pressed, all elements on the form will be reset before being applied. The resetModel actionListener will also be called.
<af:commandButton id="reset"
                     text="Reset..."
                     immediate="true"
                     actionListener="#{myUtils.resetModel}">
             <af:resetActionListener/>
</af:commandButton>
Attributes: None.
In our case, we have a form which is initially displayed as read-only. Invoking Edit button will enable the input components for user edit. Once the the user decided to cancel his edits, he will press the Cancel button and the input components should revert back to their original values.
Below is a snippet from our .jsff page:
<af:commandToolbarButton text="Cancel" id="cb2"
                                 actionListener="{viewScope.acctgEntry_edit.cancel}"
                                 icon="#{res['cancel_s']}"
                                 disabled="#{!pageFlowScope.editMode}"
                                 immediate="true">
    <af:resetActionListener/>
</af:commandToolbarButton>
Below is the sample method that resets our model:
public void cancel(ActionEvent actionEvent) {
        getOperationBinding("findAcctgEntryById").execute();
        getPageFlowScope().put("editMode", false);
        setCurrentTabClean();
}

Saturday, February 6, 2010

JSR 303 Bean Validation: Error "java.lang.NoSuchMethodError: javax.persistence.Persistence.getPersistenceUtil()Ljavax/persistence/PersistenceUtil;"

When we adopted the JSR 303 Bean Validation into our ADF/EJB project, we encountered the above error, so I posted on hibernate forums and was advised to implement my own custom class that implements TraversableResolver (Check the discussion here).
Here I will post a simple working implementation that works.
TODO:
1) Create a class that implements TraversableResolver like below:
package blogspot.soadev.validator;

import java.lang.annotation.ElementType;
import javax.validation.Path;
import javax.validation.TraversableResolver;

public class CustomTraversableResolver implements TraversableResolver {
   
    public boolean isReachable(Object traversableObject, Path.Node traversableProperty, 
                               Class rootBeanType, Path pathToTraversableObject, 
                               ElementType elementType) {
        return true;
    }

    public boolean isCascadable(Object object, Path.Node node, Class c,
                                Path path, ElementType elementType) {
        return true;
    }
}

2) Modify your code that gets the validator instance to as follows:
import blogspot.soadev.validator.CustomTraversableResolver;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
...

ValidatorFactory factory = Validation.byDefaultProvider().configure()
                      .traversableResolver( new CustomTraversableResolver() )
                      .buildValidatorFactory();
Validator validator = factory.getValidator();

Kudos to Java Powers who first attended and tested this!

Related Posts

Cheers!

Pino

EJB with EclipseLink: How to ensure that your query returns fresh data

If you wanted to retrieve always fresh data from your query, you could set the hints parameter in the @NamedQuery declaration like below:
...
import org.eclipse.persistence.config.HintValues;
import org.eclipse.persistence.config.QueryHints;
...

@NamedQuery(name = "findEmployeesByDepartmentId", 
            query = "select o from Employee o where o.department.id = :departmentId"), 
            hints = {@QueryHint(name=QueryHints.REFRESH, value=HintValues.TRUE)}

It could also be set programmatically thru the setHints() method of the Query API. An example would be:
public List findEmployeesByDepartmentId(Long departmentId){
     Query query = em.createNamedQuery("findEmployeesByDepartmentId");
     query.setParameter("departmentId", departmentId);
     query.setHint(QueryHints.REFRESH, HintValues.TRUE);
     return query.getResultList();
  }