Using an af:iterator to group and insert breaks

8 April 2008 at 12:35 CEST | In Features and tips, JDeveloper, Oracle | 9 Comments

Yesterday I got a question how to create a ADF Faces JSPX page that shows all the records from a ViewObject, but inserts group headers when one of the fields (category) in the ViewObject changes. In the end, the solution lies in the use of an <af:iterator> component to iterate the rows in the ViewObject and insert breaks where necessary.

The end result looks something like this:

Read the rest of this blog to learn how I achieved this.

First, let me show the output of the SQL statement of the ViewObject:

CODE:
ID TEXT                                     BOOL CATEGORY
-- ---------------------------------------- ---- ---------------------
7  Business Intelligence Enterprise Edition N    Business Intelligence
6  Businesss Intelligence Standard Edition  Y    Business Intelligence
8  Data Integrator                          N    Business Intelligence
1  Database Enterprise Edition              Y    Database Products
2  Database Standard Edition                N    Database Products
3  Oracle Lite                              N    Database Products
4  Application Server Enterprise Edition    N    Fusion Middleware
5  Forms and Reports                        N    Fusion Middleware

I've create a default ADF Business Components Entity Object, View Object, and Application Module. To use a checkbox I need a boolean type attribute, so I added a transient attribute to the View Object:

JAVA:
public Boolean getTrueBool() {
    return "Y".equals(getBool());
}

public void setTrueBool(Boolean value) {
    setBool(value ? "Y" : "N");
}

Next thing to do is to create the ADF Faces JSPX page to iterate over all the rows in the ViewObject and include a checkbox component for each row. Whenever the category of a row changes compared to the previous row a header should be included to indicate the start of a new category. We normally use an af:table component to show multi-record stuff, but you can't use it in this case. It does not allow to output the headers in between the rows. Hence we use the af:iterator component to iterate over the rows of the ViewObject:

XML:
<af:iterator var="row"
             value="#{bindings.TestWilfredViewIterator.allRowsInRange}">

  <af:panelHeader text="Category #{row.category}"
                  rendered="#{CategoriesBean.changed}"/>

  <af:selectBooleanCheckbox text="#{row.text}"
                            value="#{row.trueBool}"/>

</af:iterator>

The af:iterator component iterators over a binding called TestWilfredViewIterator. For each iteration it creates a variable "row" that can be used in the child components of the af:iterator.

The first child component is an af:panelheader to show a header for each new category. It's text property uses an EL expression #{row.category} to get the category text from the current row. The rendered property uses an EL expression to call out to a managed bean. That bean contains the logic if the header should be shown or not. It determines this by comparing the category with the category of the previous row. If it changed, the header should be printed otherwise not.

The next child component is an af:selectbooleancheckbox to include a checkbox input element. It's text ("label") uses an EL expression to get the description from the current row. The value is bound to the TrueBool transient attribute we added earlier. The setter of that transient attribute sets the value of the Bool attribute to either "Y" or "N".

The page definition file contains the binding declarations:

XML:
<executables>
    <iterator id="TestWilfredViewIterator" Binds="TestWilfredView"
              RangeSize="-1" DataControl="AppModuleDataControl"/>

  </executables>

It declares a iterator with the ID of "TestWilfredViewIterator". It binds to the ADF BC ViewObject "TestWilfredView" from the data control named "AppModuleDataControl". The RangeSize is set to -1 so all rows are queried and no pagination is used.

The backing bean contains the code to determine if the header should be printed:

JAVA:
public void setChanged(boolean changed) {
    throw new IllegalStateException("do not call setChanged");
}

public boolean isChanged() {
    // get the key of the current row
    FacesContext context = FacesContext.getCurrentInstance();
    Application application = context.getApplication();
    Object key =
        application.createValueBinding("#{row.key}").getValue(context);
    // compare this key with the key of the previous call (bean can be
    // called multiple times for same row)
    if (!key.equals(previousKey)) {
        // new key, compare categories
        Object category =
            application.createValueBinding("#{row.category}").getValue(context);
        changed = !category.equals(previousCategory);
        // remember category for next call
        previousCategory = category;
    }
    // remember key for next call
    previousKey = key;
    return changed;
}

The managed bean needs both a setter and a getter for the boolean property. We don't really need the setter, but it has to be there to be able to reference the bean property from an EL expression. Without the setter, it is not a bean property and you cannot reference it from EL.

It turns out that this method is sometimes called multiple times for each row of the af:iterator component. This requires some additional handling in the java code. You cannot just assume that the next call of the method also represents the next row of the af:iterator.

So, the code first gets the key of the current row of the af:iterator component. The key is compared to the key from the previous cal to isChanged(). If the key has changed (or previousKey is null), we know we have found the next row of the iterator. We then continue to get the category of the current row. We compare that to the category of the previous call to isChanged(). If it is different, we set a boolean property to indicate this row has a different category than the previous row. We need to store this in a property to make sure that subsequent calls to isChanged() for the same ViewObject row return the same result as the first call for that particular row.

That's all there's to it. Just one last thing, to make the story complete. The managed bean declaration in the faces-config.xml:

XML:
<managed-bean>
    <managed-bean-name>CategoriesBean</managed-bean-name>
    <managed-bean-class>com.example.sandbox.view.beans.Categories</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
  </managed-bean>

Tweak and Tune your JDeveloper

29 January 2008 at 11:10 CET | In Features and tips, JDeveloper, Oracle | 2 Comments

I just stumbled upon a very informative post on how to tweak and tune your JDeveloper installation. It shows some of the miracles that are tugged away in the jdev.conf file. Go read it!

Custom JSF converter to display minutes as hh:mm

8 May 2007 at 09:40 CEST | In Features and tips, JDeveloper, Oracle | 3 Comments

We have a database that stores elapsed time in number of minutes in a plain number field. In our JSF/ADF Faces application we wanted to have the user input these fields as hours:minutes. So, internal storage of 210 should be displayed as "3:30".

I knew this should be possible using a custom converter, but never looked into that. Yesterday I did and it turned out to be dead simple. Here are the few required steps:

First create a simple Java class that implements the javax.faces.convert.Converter interface:

package com.example.view.customConverters;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

import oracle.jbo.domain.Number;

public class hourMinutesConverter implements Converter {
    public hourMinutesConverter() {
    }

    public Object getAsObject(FacesContext facesContext,
                              UIComponent uiComponent,
                              String string) {
        try {
            String stringParts[] = string.split(":");
            int minutes =
                Integer.parseInt(stringParts[0]) * 60 +
                Integer.parseInt(stringParts[1]);
            Number numObj = new Number(minutes);
            return numObj;
        } catch (Exception e) {
            throw new ConverterException(e);
        }
    }

    public String getAsString(FacesContext facesContext,
                              UIComponent uiComponent,
                              Object object) {
        try {
            int totMinutes = (int)((Number)object).getValue();
            int hours = (totMinutes) / 60;
            int minutes = (totMinutes % 60);
            return hours + ":" + minutes;
        } catch (Exception e) {
            throw new ConverterException(e);
        }
    }
}

This class implements two methods: getAsObject() to convert a String (user input) to an actual Object and getAsString() to convert an actual Object to a String to display in the web page.

The error handling could be a lot better and you probably want to return meaningful error messages when the user input is not in a valid format. See the JavaDoc of javax.faces.convert.ConverterException on how to construct a ConverterException object with a simple String message or a nice javax.faces.application.FacesMessage object.

Next, go to WEB-INF/faces-config.xml and add the custom converter:


  demo.hourMinutesConverter
  
    com.example.view.customConverters.hourMinutesConverter
  

All this does is give the converter an ID which your going to use in the pages and specify the implementing class for this converter.

Finally go to the actual JSPX page and drag a Convert component from the JSF Core group in the Component Palette. Drop it on the inputText component used for your item. A dialog will appear asking your for the ID of the converter. Select "demo.hourMinutesConverter" from the list. You end up with the following code in your JSPX page:


  

I've misused the managerID field in a demo application and here is the result:

Force a language or locale for JDeveloper IDE

14 January 2007 at 11:18 CET | In Features and tips, JDeveloper, Oracle | 10 Comments

Since I live and work in the Netherlands, my workstation is setup with Dutch regional settings. It always annoyed me that JDeveloper used this setting and tried to run in Dutch. Since not everything is translated you end up with a mix of English and Dutch in your JDeveloper IDE. Error messages can be in Dutch which is not very useful for finding solutions through Google or forums.

Unfortunately JDeveloper does not let you set the language in a preference. But as it turns out it does adhere to settings in the JDEV_HOME/jdev/bin/jdev.conf. Just add the following two lines to the file to force the use of US English:

AddVMOption -Duser.language=en
AddVMOption -Duser.country=US

This does the trick

Forcing a locale with a Servlet Filter and Servlet Request Wrapper

7 December 2006 at 11:26 CET | In Features and tips, JDeveloper, Oracle | 8 Comments

We're currently developing a couple of new web applications with Oracle JDeveloper 10.1.3.0 using ADF Faces and Business Components. We keep running into problems with the Locale of the application. By default it tries to adhear to the preferred language set in the client browser. Since we're a European organization working for multiple European countries, this can result in a number of language.

But since we are an international organization that exchanges data between countries, we agreed that all applications and data have to be in English. So, we want to overrule the preferred language as set in the client browser. In ADF Faces it appears you can do this easily in the faces-config.xml:

    
      en
      en
    

This states that English is the only supported locale and also the default one. Unfortunately this does not seem to work all to well in JDeveloper 10.1.3.0. It appears that Business Components is still picking up the preferred language from the client browser. In Business Components we set a format mask on the attribute (e.g. ##0.00). When the browser is set to German or Dutch the numbers with decimals the field is initially displayed with a comma as a decimal separator. When submitting this value with a comma separator an English error message is displayed that the input does not match the specified pattern. When submitting a value with a period as decimal separator the decimals are completely removed (1.75 becomes 1). Committing a value that's too large to fit in the database column raises a German JBO-26041 error.

It has to be noted the exact same configuration in JDeveloper 10.1.3.1 works fine. The initial value is displayed with a period as decimal separator. Submitting a value with a period as separator works fine and submitting a value with a comma is rejected for not matching the pattern. Committing a too large value to the database raises a English error.

We're very close to a release, so upgrading to JDeveloper 10.1.3.1 is not an option. So I wanted another way to force the Local to be set to English-US. I ended up writing a Servlet Filter and a Servlet Request Wrapper to overrule the Accept-Language HTTP header as sent by the browser. With the filter and wrapper I can pretend as if the client sends a "Accept-Language: en" header

First comes the HttpServletRequestWrapper that wraps the client HTTP request:

package nl.eurotransplant.decimal.view.locale;

import java.util.Enumeration;
import java.util.Locale;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class LocaleRequestWrapper
        extends HttpServletRequestWrapper {
    public LocaleRequestWrapper(HttpServletRequest req) {
        super(req);
    }

    public Enumeration getLocales() {
        Vector v = new Vector(1);
        v.add(getLocale());
        return v.elements();
    }

    public Locale getLocale() {
        return new Locale("en", "us");
    }
}

Next is the Servlet Filter to install the HTTP Request Wrapper:

package nl.eurotransplant.decimal.view.locale;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class LocaleServletFilter implements Filter {
    private FilterConfig _filterConfig = null;

    public void init(FilterConfig filterConfig)
        throws ServletException {
        _filterConfig = filterConfig;
    }

    public void destroy() {
        _filterConfig = null;
    }

    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain)
        throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            HttpServletRequest req = (HttpServletRequest)request;
            LocaleRequestWrapper wrapper =
                new LocaleRequestWrapper(req);
            chain.doFilter(wrapper, response);
        } else {
            chain.doFilter(request, response);
        }

    }
}

When creating a Servlet Filter, JDeveloper probably already installed the servlet filter in the web.xml. You have to specify a URL pattern to match for this filter. Set this to '*' to match all requests. By default the filter-mapping is appended as last in the web.xml. This means this filter is triggered after the ADF filter, which is too late. So you have to edit the web.xml directly to put the filter-mapping for the servlet filter on top:

    
        LocaleServletFilter
        nl.eurotransplant.decimal.view.locale.LocaleServletFilter
    
    
        LocaleServletFilter
        *
    
    
        adfBindings
        *.jsp
    
    
        adfBindings
        *.jspx
    
    
        adfFaces
        *.jsp
    
    
        adfFaces
        *.jspx
    

Run your application and see how it picks up the English language, whatever your preferred language in the client browser is. For us, this is a good solution for the problem we have in 10.1.3.0. In version 10.1.3.1 you can seem to get the same effect with only allowing English in the faces-config.xml:

  
    
      en
      en
    
  

There's no need to install the servlet filter and http request wrapper in 10.1.3.1

Jonas Jacobi on JSF

22 September 2005 at 03:54 CEST | In Features and tips, JDeveloper, OpenWorld 2007, Oracle | 3 Comments

By far the most interesting session I attended today was "Everything you need to know about JavaServer Faces and ADF Faces" by Jonas Jacobi. He gave a nice overview of the standard JavaServer Faces (JSF) and Oracle's ADF Faces which mainly offer a lot of extra JSF compliant components.

I really encourage you to have a look at the JSF site on OTN. Have a special look at the link where Jonas introduces ADF Faces. You can find this link in the "Meet the experts" box at the right hand top corner. It's a 49 minute webinar on JSF and shows a neat example of the rich internet applications (RIA) you will be able to build with (future releases of) ADF Faces. Jonas shows a file explorer that he also showed today in the session. It gives a good idea of what will be possible with ADF Faces.

Throughout Oracle OpenWorld I've seen several demos of Oracle Bank, a demo application showing some great features of ADF Faces and how rich a thin client can be. I couldn't find a viewlet of this on OTN (yet), so I will at least show a picture of the screen. You can click on the pic for a larger view, or even download the original picture. There where a lot of impressive things in this demo: sliders, you can collapse en expand entire "frames" (you can see the upper search frame collapsed, the middle select frame expanded and the bottom complete frame collapsed), selecting another row in the table automatically refreshed the graph, a very slick look-and-feel and much, much more.

The other two session I went to were less interesting. One gave an overview of the new features in Application Server 10gR3 and most of it were things I already knew although I heard a couple of minor new things. The other session was a real disaster and not very useful. Today we also had the keynote by Larry Ellison and I mus say that I had expected a bit more from that. Not that it was bad and not that I know what I was expection but it was all a bit tame. Perhaps that's what they have to do when there is so much at stake.

Just aside: I'm not really an Applications type of guy. I'm more from a custom development background. This means I do not express a lot of interest in all the JD Edwards/Peoplesoft/Oracle EBS things going on at OpenWorld. However, I did notice a truck running up and down the street before the Moscone center all day. Could it be that Microsoft is being nervous?

OpenWorld: Project Raptor – watch out TOAD

20 September 2005 at 00:53 CEST | In Database, Features and tips, Features and tips, JDeveloper, OpenWorld 2007, Oracle | 7 Comments

Oracle released an Early Access version of project Raptor. This made much of the following article obsolete as you can now see for yourself.

I just attended a session about new developments by the Server Technologies’ Database Division. One of the most interesting things (for me) was the short demo of project Raptor. This is the long awaited visual tool for database development. It could be a real competitor for the well known TOAD product from Quest. They did a short demo in which you could view and alter all the known database objects. The also showed some PL/SQL development features including code formatting and a powerful debugger. The session was also about other new developments in database 10gR2 and coming releases. This mean there was only a couple of minutes to demo project Raptor but it was really interesting. A lot of people approached the speaker afterwards to ask about Raptor.

It looked very familiar to me (knowing JDeveloper). It doesn’t require an Oracle client, just as JDeveloper. It’s just a matter of unzipping and start running. Looking at the screen it looks like it just makes a JDBC connection. It’s based on the JDeveloper framework but it is much more powerful then the existing database and PL/SQL editing features in JDeveloper. As I understood it will eventually replace the current database and PL/SQL editing in JDeveloper in some feature release. But before that, project Raptor will be offered as a standalone product on OTN. Due to all the new regulations Oracle people cannot give estimated delivery dates other then “this fiscal year”. For Oracle that means before May 31st 2006, but the speaker told me he expects it on OTN very soon.

I used my digital camera to make some photos of the demoed screens. The unfortunate thing is that I forgot to bring my USB cable to the Moscone center, so I can’t upload the pictures before this evening. Check back again to see the screenshots.

Some of the other things discussed in the session were Oracle Secure Backup, Patch distribution by Enterprise Manager Grid control 10gR2, TimesTen in-memory database, XQuery, Transparent data encryption, HTML-DB and two new security features: AuditVault and DataVault.

To me, TimesTen and AuditVault were the most interesting as they seemed to target actual business problems we are having right now. I’ll have to look into both technologies when I’m back at the office. I guess attending OpenWorld will take more then just the one week I’m here. I get the feeling I have a couple of weeks work when I get back in checking out all the new stuff I discover here.

PS. On a more personal note: congratulations to my wife for our second wedding anniversary. Couldn’t think of a better/more romantic way to celebrate it than to be on my own in San Francisco visiting a tech conference ;-)

Update 19-sep-2005 18:30: I've just returned to my hotel room to connect my digital camera to my laptop and upload the pictures. I didn't want to use my flash when making the pictures during the session so I needed quite a long shutter time. Unfortunately that made the first picture kind of blurry. Nonetheless I will post all three pictures below. You can click on any of the pictures to get a bit larger view.

If you want to get the full size images, you can find them here, here and here.

The first screenshot is the blurry one but you can (hardly) see the tree control showing all database objects and the code editor for stored PL/SQL. The second shot shows the context menu including the Format SQL option for code formatting. The third and final shot shows the debugger in action. You can see all variables in the lower right corner. By the way: hovering over a variable in the code editor will show its value as a tooltip. You can also see the debug console at the lower end of the screen and the call stack in the lower left. I must say Raptor looked very promising for the five minutes we got to look at it. Let's hope Oracle will show it at the demogrounds later this week and for the screenshots Brian has promised.

I'm off to the OTN underground event. Check back tomorrow for more updates on OpenWorld.



Update 21-sep-2005: Raptor was just briefly demoed during the keynote of Chuck Rozwat. Unfortunately I was watching the keynote in an overflow room where the screen is too small to make any good pics. If anyone attended the keynote in the big room and made some usable pics, you can contact me to put them up here.
I'll have a look at Raptor at the HTML DB demoground myself, but I doubt they will let me near it with a camera :-)

Update 22-sep-2005: Brian Duff has just published some real screenshots for project Raptor. These actually have some detail compares to mu blurry pictures.

Update 31-dec-2005: Added a comment at the top of the article about the release of an Early Access version of project Raptor.

Reset SSO password and send by email

13 July 2005 at 10:13 CEST | In AppServer, Features and tips, JDeveloper, Oracle | 3 Comments

We're finishing up deploying Oracle Single Sign On. Our version of SSO (v9.0.4.1.0) comes with default pages to let a user reset his password when he forgot about it. These pages ask the user for a "secret question". The user is allowed to change his password if he gives the correct answer to this question.

I want a page which will just generate a new random password for a user and sends it by email to the registered address. Because of our SSO policies, the user has to change this password again on the first login. Secure enough for me.

The documentation states that Oracle does not feel it is secure to send a new password by email and that's why the chose for the option with the secret question. In my opinion having a week secret question is even more risky then sending a password by email. Who doesn't remember the hack on Paris Hilton's T-Mobile account? Besides that, it is quite a hassle to have all your users set up a secret question in the first place. We converted some 2000 users from other systems to SSO and how do I get them to setup a secret question?

I also asked Oracle support and there is no default option in Oracle Application Server to implement a reset-and-email-password page. Searching a bit more on the OTN forums resulted in a bit of code that inspired me to writing the page myself.

I changed the code from OTN to ask for a username and email address. It first validates if that combination exists in the Oracle Internet Directory. If so, the password is changed to a random generated password and this new password is sent by email to the user.

If you look at the code, you see I also used Context to look up environment settings. This is because I do not know the password of orcladmin for the production OID. Besides that, I do not want to hard code any username/password in this JSP. That's why I'm using environment settings. You set these in the web.xml and the administrator of the application server can change these values after deployment. That makes it possible to use the same EAR file on both development, testing and production application servers with just different configurations.

The JSP is below. You would probably have to add things for a nicer form, error handling, etc. The code here is just and example and should be enough to get you started.

< %@ page contentType="text/html;charset=windows-1252"%>
< %@ page import="java.util.*,java.io.*, javax.naming.*,
                  javax.naming.directory.*"%>

< %
  // **********************************************************
  // forums.oracle.com/forums/thread.jsp?forum=47&thread=293082
  // for the original code
  // **********************************************************

  String username = new String();
  String email    = new String();

  // only process if the form is submitted
  if (request.getParameter("username") != null &&
      request.getParameter("email") != null) {
    // get settings from environment
    Context initial = new InitialContext();
    Context env = (Context) initial.lookup("java:comp/env");

    String searchBase  = (String) env.lookup("oidSearchBase");
    String oidAdminDN  = (String) env.lookup("oidAdminDN");
    String oidAdminPwd = (String) env.lookup("oidAdminPwd");
    String ldapServer  = (String) env.lookup("LDAPServer");
    String ldapPort    = (String) env.lookup("LDAPPort");
    String smtpHost    = (String) env.lookup("smtpHost");
    String smtpSender  = (String) env.lookup("smtpSender");
    String smtpSubject = (String) env.lookup("smtpSubject");
    String smtpContent = (String) env.lookup("smtpContent");

    // get parameters from HTTP request
    username=request.getParameter("username");
    email   =request.getParameter("email");
    String userDN = "cn=" + username + ", " + searchBase;

    // generate new random password
    StringBuffer newPassword= new StringBuffer("");
    Random rnd = new Random();
    final String alphabet =
                 "0123456789abcdefghijklmnopqrstuvwxyz";
    for (int i=0; i<10; i++) {
      newPassword.append(alphabet.charAt(
        rnd.nextInt(alphabet.length())));
    }
    // add two digits to be sure to pass password policy
    newPassword.append(alphabet.charAt(rnd.nextInt(10)));
    newPassword.append(alphabet.charAt(rnd.nextInt(10)));

    // setup OID connection
    DirContext ctx = null;
    Properties env = new Properties();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL,
            "ldap://" + ldapServer + ":" + ldapPort + "/");
    env.put(Context.SECURITY_PRINCIPAL, oidAdminDN);
    env.put(Context.SECURITY_CREDENTIALS, oidAdminPwd);
    try {
      ctx = new InitialDirContext(env);
    }
    catch (NamingException e) {
      response.sendError(500, "LDAP logon error");
    }

    // retrieve email address of user
    String ldapEmail = null;
    try {
      BasicAttributes currentAttr =
        (BasicAttributes) ctx.getAttributes(userDN);
      ldapEmail = (String) currentAttr.get("mail").get();
    }
    catch (NamingException e) {
      // user not found in OID
      response.sendError(500, "User not found");
    }

    if (email!=null && email.equalsIgnoreCase(ldapEmail)) {
      // email addresses match, reset password
      BasicAttributes newAttr = new BasicAttributes();
      // newAttr.put("userpassword", newPassword.toString());
      try {
        ctx.modifyAttributes(userDN,
          DirContext.REPLACE_ATTRIBUTE, newAttr);
        // send email to the user
        sendMail.setHost(smtpHost);
        sendMail.setSender(smtpSender);
        sendMail.setRecipient(ldapEmail);
        sendMail.setSubject(smtpSubject);
        sendMail.setContent(smtpContent.replaceAll("%password%"
          , newPassword.toString()).replaceAll("\\\\n","\n"));
        sendMail.sendMessage(pageContext);
      }
      catch (NamingException e) {
        response.sendError(500, "Error changing password");
      }
    } else {
      // supplied email address does not match the one in OID
      response.sendError(500, "User not found");
    }

  }
%>

  

  
  

Forgot Password

Username
Email address

Powered by WordPress with Pool theme design by Borja Fernandez.
Entries and comments feeds.