Disclaimer

The views and opinions expressed in this blog are my own and do not necessarily reflect those of my employer. The views and opinions expressed by visitors to this blog are theirs and do not necessarily reflect my own

Tuesday, June 21, 2011

GWT : DisclosurePanel Images, IE6 and SSL

I recently worked on a project where we were using standard GWT Disclosure Panels and Tree components. When the application was sitting behind SSL we found that the arrow images were not visible in IE6. What was more frustrating was that in every other browser the images displayed correctly.

After a lot (and I mean a lot) of time I stumbled across the API documentation for image bundles which is the mechanism/technique Google have used to package up images for widgets into a composite image that help reduce calls to the server and speed up the user experience.
In summary, the problem is that GWT makes use an activeX control to process png transparency on IE6. Internet Explorer specifies that files which require the plugin for viewing must be cached to disk by the browser. By default the HTTP headers set by the GWT servlets when the image is requested specify that the payload should not be cached. The image then never stored to disk (cached) and the activeX control cannot process the image and display it to the page.

The solution is actually quite simple. You need to create an additional servlet that executes when image resources are requested. This is done by setting up a servlet in the web.xml with a file filter.

package xxxx.xxxxx.xxxxx;

public class ImageServlet extends HttpServlet {

 private boolean isIE6(final HttpServletRequest request){
  final String userAgent = ((HttpServletRequest) request).getHeader("User-Agent");
  return userAgent.contains("MSIE 6");
 }

 protected doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {

  if (isIE6(request) ){
   response.setHeader("Cache-Control", "");
   response.setHeader("Pragma", "" );

   // This should be some date in the future. Dynamically create the
   // date part of the string to be something in the future e.g.
   // sysdate + 1
   response.setHeader("Expires","Thu, 1 Jan 2013 00:00:00 ");
  }

  final String uri = request.getRequestURI();

  // Extract the icon name
  final String iconName = uri.substring(request.getContextPath().length(), uri.length() );

  // Supply the icon back to the browser
  continueProcessing(request, response, iconName);
 }

 protected void continueProcessing(final HttpServletRequest request
    , final HttpServletResponse response
    , final String iconName){

  final ServletContext context = getServletContext();
  final InputStream is  = context.getResourceAsStream(iconName);
  final OutputStream os = response.getOutputStream();
  response.setContentType("image/png");

  byte[] = new byte[8912];

  while (true){
   final int l = is.read(b);
   if (l < 0)
    break;
   os.write(b,0,l);
  }
 }
}
The servlet itself should look something like that below :

 ImageServlet
 xxxx.xxxxx.xxxxxx.ImageServlet


 ImageServlet
 *.png


You then need to set up the servlet mapping in your web.xml so that all png requests get routed through this new servlet. The example could be improved by adding caching of images requested. This would also speed up load performance of your application for images that are requested more than once.

Saturday, June 04, 2011

JBOSS, GWT and Request Factory : Unknown Entity Exception

I've been playing around with RequestFactory, GWT's data centric transport/serialization mechanism and was really struggling to set up a new project that could pull data from an existing set of database tables and I wanted to share my experiences if you decide to use an existing schema.

The tutorial can be found over on the GWT developer documentation and I don't want repeat what can be found there.

I decided to use an Oracle XE database and the world famous HR sample database, creating JPA entities for the EMPLOYEES and DEPARTMENTS tables.

JPA/Hibernate benefits from having a version column that is updated each time the object is persisted. RequestFactory uses this to determine whether changes have happened on the entity.

The first problem I encountered was that I kept seeing "Unknown Entity Exception" in the server logs (JBoss-6.0.0-Final). This was eventually solved by ensuring that the employees table had a "VERSION" column of type NUMBER(38). The annotated JPA class also requires the type to be of type Integer (Use the boxed/wrapper type and not int).

Initially I tried using Long for the "VERSION" column however that produces more errors in the logs and it was quickly evident this needs to be set to an Integer to be compatible with the @Version annotation.

Below is my entity definition that maps to the employees table.

@Entity
@Table(name = "EMPLOYEES", schema = "HR")
public class Employees implements Serializable {

 private static final long serialVersionUID = 1717056757381711656L;
 private static final Logger logger = Logger.getLogger(Employees.class);

 @Id
 @Column(name = "EMPLOYEE_ID")
 @NotNull
 private Long id;

 @Column(name = "VERSION")
 @Version
 private Integer version;

 @Column(name = "FIRST_NAME")
 private String firstName;

 @Column(name = "LAST_NAME")
 private String lastName;

 public Long getId() { return id; }

 public void setId(Long id) { this.id = id; }
 
 public Integer getVersion() { return version; }

 public void setVersion(Integer version) {this.version = version; }
.....
}

Finally you'll need to ensure that all rows in your schema are initialized with a default version. A simple update statement solves this:

update EMPLOYEE set version = 0;

While trying to solve this problem google searches led me down the garden path suggesting that one checks the JPA annotated class imports javax.persistence.Entity and not org.hibernate.persistence.Entity. Other searches suggested it was a incorrect configuration when using non-container based persistence. This was also not the problem as I was using a JTA datasource. The configuration for which is below.


  
    hrDS
    jdbc:oracle:thin:@localhost:1521:xe
    oracle.jdbc.driver.OracleDriver
    HR
    password
    org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter
      
         Oracle9i
      
  


My persistence.xml file was as follows:


    
        org.hibernate.ejb.HibernatePersistence
        java:hrDS
        
            
            
            
               
        
    
 

Again hope that helps someone else out there.