Monday, January 31, 2011

Preventing multiple form submits in ICEFaces by disabling commandButton

I created a form in ICEFaces 2 to upload a file. ICEFaces 2 has a new component for files, called the fileEntry (see http://wiki.icefaces.org/display/ICE/FileEntry). The old inputFile component has been removed from ICEFaces 2. Uploading a file takes a while, and I quickly noticed the user could click the "upload" button multiple times while the upload was in progress. This is obviously something you want to prevent. There is a very easy approach to this: just disable the submit button once it has been pressed. All you need to do is add this code to the commandButton tag:
onclick="this.disabled=true"
This works fine, and if you always go to a new page after the form has been submitted this is all you have to do. In most applications this unfortunately won't be the case: if something goes wrong with the file upload or if some other form components fail validation, you will want to stay on the same page, giving the user a warning message and allowing him to try again. The problem is that your commandButton is now disabled, so the user can't try again...
Re-enabling the commandButton when the action finishes is not as easy as it may seem: there's no "onAfterSubmit" action or something similar on the form. My first try was to add the disabled property with a value of "false":
disabled="false"
I thought that ICEFaces would re-evaluate the form after the submit, causing it to reset the disabled property to false. Unfortunately ICEFaces is smarter than that, and uses a technique called "partial dom updates". What this means is it checks if any of the components properties were changed in the action and it will only update those components whose properties have actually changed. This is a smart technique, since it reduces http traffic. It also means that our commandButton will not be updated, since for ICEFaces nothing changed to this component (it isn't aware of the JavaScript function we performed on it). This means we will have to trick ICEFaces into thinking a property of our commandButton has actually changed, causing it to refresh the commandButton. I choose to "abuse" the styleClass property for this use. I set it to:
styleClass="#{backingBean.randomValue}"
Then in my backing bean I implemented the getter for this value as follows:
public String getRandomValue() {
return "a" + System.currentTimeMillis();
}
This ensures we will get a different value for the styleClass property on each form submit. This will trick ICEFaces into thinking the commandButton has changed, causing it to refresh it and setting the disabled value to false, effectively re-enabling our commandButton.

I agree this approach isn't very clean, but it's the only technique I found to accomplish this in ICEFaces. If anyone knows of a better way, I'd be glad to know!

Monday, January 24, 2011

Avoiding the "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed" error

Please note: this post focuses on the standard Java https implementation. If you are using the Apache HttpClient library, please see this post.

The SSLHandshakeException is thrown by java when the host you are trying to contact doesn't have a valid SSL certificate for that hostname. Most of the time this is very useful, since it means something on that host is wrong (the certificate has expired, the machine you're contacting is not who it is pretending to be etc...). However, in development mode you often don't want to pay for a "real" certificate, signed by a CA (certificate authority) like Verisign. You will then use a self-signed certificate, which gets rejected by java. It's for these cases that we're going to build a workaround. Please note that you should probably not use this code in a production environment. If you do, there's no reason to use https, since you're bypassing its functionality and you might just as well stick to http.

The first thing we need to do is create a custom TrustManager for SSL. SSL uses a protocol called X.509 (see http://en.wikipedia.org/wiki/X.509 for more info on this). We will build a TrustManager that trusts all servers:
X509TrustManager tm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}

@Override
public void checkServerTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) throws CertificateException {

}

@Override
public void checkClientTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) throws CertificateException {
}
};
As you can see, the checkXXXTrusted() methods throw Exceptions when something is wrong. We never throw an exception, effectively trusting all hosts.

The next thing we'll need to do is use this TrustManager on an SSLContext. An SSLContext (http://download.oracle.com/javase/1.5.0/docs/api/javax/net/ssl/SSLContext.html) is a factory class that is used to create socket factories, which in their turn create the actual ssl sockets used to communicate with the server. Here's how we do this:
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[] { tm }, null);
SSLContext.setDefault(ctx);

There now remains one more thing to be done: set a custom HostnameVerifier. A HostnameVerifier (http://download.oracle.com/javase/1.5.0/docs/api/javax/net/ssl/HostnameVerifier.html) is a class that makes sure the host you are contacting doesn't use a spoofed URL. We will again build a HostnameVerifier that trusts all hosts:

HttpsURLConnection conn = (HttpsURLConnection) new URL("https://serverAddress").openConnection();
conn.setHostnameVerifier(new HostnameVerifier() {

@Override
public boolean verify(String paramString, SSLSession paramSSLSession) {
return true;
}
});


Again, this HostnameVerifier will trust all hosts.

Putting all our code together, the final class will look like this:
public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException, MalformedURLException, IOException {
X509TrustManager tm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}

@Override
public void checkServerTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) throws CertificateException {

}

@Override
public void checkClientTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) throws CertificateException {
}
};
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[] { tm }, null);
SSLContext.setDefault(ctx);  
HttpsURLConnection conn = (HttpsURLConnection) new URL("https://serverAddress").openConnection();
conn.setHostnameVerifier(new HostnameVerifier() {

@Override
public boolean verify(String paramString, SSLSession paramSSLSession) {
return true;
}
});
conn.connect();

}

One final note: I prefer the way the Apache HttpClient library handles this. In the HttpClient library you can make a clean separation between the ssl verification logic and the code that does the actual work. This allows you to easily remove the code in the production environment or to use a switch between the development and production environment. This is much harder in the plain java version, since the code is more entangled. See this post for how to do this with the Apache HttpClient.

Saturday, January 22, 2011

ICEFaces 2.0 charts migration

Today I was upgrading an ICEFaces 1.8 / JSF1.2 project to ICEFaces 2 and JSF 2. Everything worked well, except my charts weren't displayed anymore. After snooping around in the ICEFaces code, I found out that they used a new JSF 2 mechanism to serve the chart images, called resource handlers. The problem is that the ICEFaces resource handler URL isn't handled by the JSF servlet. This means the resource handler never gets executed. To make sure it gets executed, you should add the mapping to your web.xml file as follows:
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/icefaces/*</url-pattern>
</servlet-mapping>

This will make the charts visible again. The same goes for the DataExporter component, which also doesn't work as long as you don't add this mapping.

Monday, December 20, 2010

Embedding fonts into PDF generated by JasperReports

Today I was creating a report that needed to be exported to PDF. I used the de facto Java standard for when you need a good report: JasperReports (http://jasperforge.org/projects/jasperreports). To make things easier for me, I used the iReport report designer (http://jasperforge.org/projects/ireport), which is a WYSIWYG tool to create JasperReports reports.

I used different fonts, some bold, some regular. The output looked really good in the iReport preview. Then I ran my application and created the PDF. All the fonts and font styles (bold, italic) were gone and had been replaced by one plain font. After googling a bit, I found the cause of the problem: the fonts I used were not embedded in the generated PDF, so they could not be shown correctly.

I will now explain how you can embed fonts in your PDF. The methods described here work on JasperReports 3.7 and greater, so make sure you have a recent version.

There are two methods to embed the fonts in your PDF. Both methods yield the same result, but one method requires the use of iReport. I will first explain the method without iReport, then the method with iReport. If you use iReport, I strongly suggest using the method with iReport, since it is much easier.

1. No iReport (the hard way)


We will use a JasperReports extension, called the simple font extension. This extension registers the necessary fonts with JasperReports so it can embed them in a PDF when needed. The first thing you need to do is to create a file called jasperreports_extension.properties. This file should have the following content (I broke up the lines so they fit the screen, don't do this yourself):


net.sf.jasperreports.extension.registry.factory.fonts=
net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory
net.sf.jasperreports.extension.simple.font.families.myfamily=
fonts/myfamily.xml


Then you'll need a file called myfamily.xml in a fonts folder. This is what the content should look like:

<?xml version="1.0" encoding="UTF-8"?>
<fontFamilies>

<fontFamily name="Arial">
<normal><![CDATA[fonts/arial.ttf]]></normal>
<bold><![CDATA[fonts/arialbd.ttf]]></bold>
<italic><![CDATA[fonts/ariali.ttf]]></italic>
<boldItalic><![CDATA[fonts/arialbi.ttf]]></boldItalic>
<pdfEmbedded><![CDATA[true]]></pdfEmbedded>
</fontFamily>


<fontFamily name="Tahoma">
<normal><![CDATA[fonts/tahoma.ttf]]></normal>
<bold><![CDATA[fonts/tahomabd.ttf]]></bold>
<pdfEmbedded><![CDATA[true]]></pdfEmbedded>
</fontFamily>


</fontFamilies>

As you can see, I embedded two fonts here: Arial and Tahoma. Arial has a regular, bold and italic variant, while Tahoma lacks the italic variant. I set both pdfEmbedded to true, to make sure they get embedded into pdfs. You will of course also have to copy the ttf files to the fonts directory you created earlier. You will have to locate the ttf files on your harddisk. For windows, they are located in \windows\fonts.

Now all you need to do is add all the files we created and copied to the classpath of your application. To sum it up, you'll have the following files on your classpath:
  • jasperreports_extension.properties: This file needs to go in the root of your classpath (so not in a package). It will be automatically picked up by JasperReports.
  • fonts/myfamily.xml: The xml file defining the fonts. It should be named as you declared it in the .properties file.
  • fonts/*.ttf: All the ttf files you reference in your xml file


2. Using iReport (the easy way)


Everything we did in the first method can be automated using iReport. The first thing you need to do is make sure the all the fonts you used in your report have been defined in iReport. You can do this by starting iReport and going to Tools -> Options -> iReport -> Fonts. You will see the following window:



If the fonts you used are not yet installed (they probably aren't), you'll need to install them. Click the "Install Font" button. A wizard will pop up, asking you the font name and the location of the ttf files. Just direct the application to the location of the ttf files (for windows systems this is generally in the \windows\fonts folder). Make sure to check the "Embed this font in PDF" checkbox on the second page! All other settings can be left at their defaults. Once all the fonts are installed, you select all the fonts you used in your report (hold ctrl to select multiple entries from the list). Then you click the "Export as extension" button. This will create a jar file. All you need to do is add the created jar file to the classpath of your application and you're done!

Saturday, August 7, 2010

PayPal Mass Payments using HttpClient 4

Here's another PayPal tip. The PayPal Mass Payments feature allows anyone with a Premier or Business account to send multiple payments instantly—saving time, money and the hassle of having to individually send funds to every payment recipient.

In this tip I will show you how to make a mass payment using the apache httpclient library (http://hc.apache.org/httpcomponents-client/index.html), version 4.

First we define some constants we'll need in the code:

private NumberFormat paypalNumberFormat = new DecimalFormat("0.00", new DecimalFormatSymbols(Locale.US));
private static final String PAYPAL_URL = "https://api-3t.sandbox.paypal.com/nvp"; //Remove the ".sandbox" if you want to use the live environment
private static final String PAYPAL_USER = "username";
private static final String PAYPAL_PASSWORD = "password";
private static final String PAYPAL_SIGNATURE = "signature";

Here's the code to make the actual call:

HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(PAYPAL_URL);
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("USER", PAYPAL_USER));
params.add(new BasicNameValuePair("PWD", PAYPAL_PASSWORD));
params.add(new BasicNameValuePair("SIGNATURE", PAYPAL_SIGNATURE));
params.add(new BasicNameValuePair("VERSION", "2.3"));
params.add(new BasicNameValuePair("METHOD", "MassPay"));
params.add(new BasicNameValuePair("RECEIVERTYPE", "EmailAddress"));
params.add(new BasicNameValuePair("L_EMAIL0", email));
params.add(new BasicNameValuePair("L_AMT0", paypalNumberFormat.format(amountToSend)));
params.add(new BasicNameValuePair("CURRENCYCODE", "USD"));
post.setEntity(new UrlEncodedFormEntity(params));
HttpResponse response = client.execute(post);

This sample will send 1 payment to the PayPal account in the email variable and with the amount in the amountToSend variable. The parameters are called L_EMAIL0 and L_AMT0. Since this is the mass payments API, you can send more than one payment. To do so, just add 2 new parameters for each payment you want to make: L_EMAILx for the PayPal account you want to send the money to and L_AMTx for the amount you want to transfer. x is a number from 0 to 255, making the maximum number of simultaneous payments you can make 256.

PayPal sends back a couple of parameters. You will probably want to check the "ACK" parameter, which tells you if your call succeeded. See https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_nvp_NVPAPIOverview for an overview of the parameters and their values returned by PayPal. Here is a way to parse the response:

private Map<String, String> parseResponse(HttpResponse response) throws IllegalStateException, IOException {
Map<String, String> map = new HashMap<String, String>();
String responseStr = getResponseContent(response);
List<NameValuePair> responseParams = new ArrayList<NameValuePair>();
URLEncodedUtils.parse(responseParams, new Scanner(responseStr), "UTF-8");
for (NameValuePair nvp : responseParams) {
map.put(nvp.getName(), nvp.getValue());
}
return map;
}

private String getResponseContent(HttpResponse response) throws IOException, IllegalStateException {
InputStream is = response.getEntity().getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String result = "";
String line = null;
while ((line = br.readLine()) != null) {
result += line;
}
return result;
}

You can call it like this:

Map<String, String> responseParams = parseResponse(response);
String ack = responseParams.get("ACK");
if (ack != null && ack.indexOf("Success") >= 0) {
//Do something
} else {
//Do something else
}

Tuesday, July 27, 2010

Slow ICEFaces

ICEfaces (http://www.icefaces.org) is a great JSF framework. I recently developed an application using ICEfaces. On my development machine, everything worked well. However, when I deployed the application to our Linux test server, the whole application became very slow (I'm talking about 20 seconds per request).
After profiling the application, I found out that ICEfaces wraps each HttpServletRequest object to have a uniform interface over all the different Servlet specification versions. It caches some of the values from the HttpServletRequest, and there lies the culprit: one of the values it caches is the getLocalName() value. How this method is implemented depends on your Servlet container, but on apache it results in a call to InetAddress.getHostName(). InetAddress.getHostName() will perform a reverse DNS lookup on its IP address. And there lies the problem: our test server doesn't have a DNS entry, so this method fails (after trying for a very long time). Java will also not cache failed DNS lookup values (you can enable this, see http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/guide/net/properties.html, but I wouldn't recommend doing so).

So there are 2 possible solutions:
  1. Add a DNS entry for your server (either in your DNS server or in the etc/hosts file or similar)
  2. Sometimes modifying the DNS is not possible (for any number of reasons). Another solution would be to create a Filter that overrides the default behavior of HttpServletRequest.getLocalName(). Here's an example:

public class LocalNameFilter implements Filter {

public void destroy() {
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest hsr = (HttpServletRequest) request;  
ServletRequestWrapper wrapper = new HttpServletRequestWrapper(hsr) {
@Override
public String getLocalName() {    
return "myHost";
}
};
chain.doFilter(wrapper, response);
}

public void init(FilterConfig fConfig) throws ServletException {
}

}

Don' forget to declare this filter in your web.xml:
<filter>  
<display-name>LocalNameFilter</display-name>
<filter-name>LocalNameFilter</filter-name>
<filter-class>com.xxx.web.LocalNameFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LocalNameFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Saturday, July 24, 2010

Avoiding the "javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated" with HttpClient

Please note: this post focuses on the Apache HttpClient library. If you're using plain java, please see this post.

When developing a https application, your test server often doesn't have a (valid) SSL certificate. This will cause the following exception to be thrown when connecting your client to the test server: "javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated".

I will be discussing a way to fix this issue with the apache HttpClient, version 4.0.1 (http://hc.apache.org/httpcomponents-client/).

1. Bits and pieces


You usually create your HttpClient like this:

client = new DefaultHttpClient();

We will need to tell the client to use a different TrustManager. A TrustManager (http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/api/javax/net/ssl/TrustManager.html) is a class that checks if given credentials (or certificates) are valid. The scheme used by SSL is called X.509 (http://en.wikipedia.org/wiki/X.509), and Java has a specific TrustManager for this scheme, called X509TrustManager. First thing we will need to do is create such a TrustManager:

X509TrustManager tm = new X509TrustManager() {

public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}
};

As you can see, this code doesn't do much: if a certificate is invalid the TrustManager is supposed to throw a CertificateException in the checkXXX methods. Since we always want to accept all certificates, we never throw an exception.

Next we need to find a way to set this TrustManager in our HttpClient. The TrustManager is used by the SSL sockets. Sockets are created using a SocketFactory. For SSL sockets this is an SSLSocketFactory (http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/api/javax/net/ssl/SSLSocketFactory.html). When creating a new SSLSocketFactory, you need to pass an SSLContext to the constructor. It is this SSLContext that will contain our newly created TrustManager.

First thing we need to do is get an SSLContext:

SSLContext ctx = SSLContext.getInstance("TLS");

TLS is the successor to SSL, but they use the same SSLContext.

Then we initialize this context with our new TrustManager that we created above:

ctx.init(null, new TrustManager[]{tm}, null);

We can then finally create our SSLSocketFactory:

SSLSocketFactory ssf = new SSLSocketFactory(ctx);

Now we still need to register this SSLSocketFactory with our HttpClient. This is done in the SchemeRegistry of the ConnectionManager of the HttpClient:

ClientConnectionManager ccm = base.getConnectionManager();
SchemeRegistry sr = ccm.getSchemeRegistry();
sr.register(new Scheme("https", ssf, 443));

We register a new Scheme, with the protocol https, our newly created SSLSocketFactory which contains our TrustManager and we tell the HttpClient that the default port for https is port 443.

2. Putting it all together


The following class takes a HttpClient and returns a new HttpClient that accepts any SSL certificate:

/*
This code is public domain: you are free to use, link and/or modify it in any way you want, for all purposes including commercial applications. 
*/
public class WebClientDevWrapper {

public static HttpClient wrapClient(HttpClient base) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {

public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = base.getConnectionManager();
SchemeRegistry sr = ccm.getSchemeRegistry();
sr.register(new Scheme("https", ssf, 443));
return new DefaultHttpClient(ccm, base.getParams());
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
}

You can then do something like this in the code that creates the HttpClient:
this.client = new DefaultHttpClient();
if(dev) {
this.client = WebClientDevWrapper.wrapClient(client);
}

Update

In some exceptional cases, the method described above doesn't work. This is due to the Apache AllowAllHostnameVerifier still being to strict. In this case, you will need your own X509HostnameVerifier. Create it as follows:
X509HostnameVerifier verifier = new X509HostnameVerifier() {

                @Override
                public void verify(String string, SSLSocket ssls) throws IOException {
                }

                @Override
                public void verify(String string, X509Certificate xc) throws SSLException {
                }

                @Override
                public void verify(String string, String[] strings, String[] strings1) throws SSLException {
                }

                @Override
                public boolean verify(String string, SSLSession ssls) {
                    return true;
                }
            };
Then set it on your socket factory:
ssf.setHostnameVerifier(verifier);
If we put everything together, the new code looks like this:
/*
This code is public domain: you are free to use, link and/or modify it in any way you want, for all purposes including commercial applications. 
*/
public class WebClientDevWrapper {

    public static HttpClient wrapClient(HttpClient base) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {

                public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                }

                public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                }

                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };
            X509HostnameVerifier verifier = new X509HostnameVerifier() {

                @Override
                public void verify(String string, SSLSocket ssls) throws IOException {
                }

                @Override
                public void verify(String string, X509Certificate xc) throws SSLException {
                }

                @Override
                public void verify(String string, String[] strings, String[] strings1) throws SSLException {
                }

                @Override
                public boolean verify(String string, SSLSession ssls) {
                    return true;
                }
            };
            ctx.init(null, new TrustManager[]{tm}, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(verifier);
            ClientConnectionManager ccm = base.getConnectionManager();
            SchemeRegistry sr = ccm.getSchemeRegistry();
            sr.register(new Scheme("https", ssf, 443));
            return new DefaultHttpClient(ccm, base.getParams());
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
}

You can then do something like this in the code that creates the HttpClient:
this.client = new DefaultHttpClient();
if(dev) {
this.client = WebClientDevWrapper.wrapClient(client);
}