Tuesday, December 3, 2013

JSF 2 App on Google App Engine, EclipseLink JPA, Google Cloud SQL

There are many articles on the web on how to put up a JSF application on Google App Engine. I always found that many of the articles were not complete especially when it came to setting up EclipseLink with Google Cloud SQL.

There were some major hurdles that I overcame and I wanted to post the results of all my hard work. Hopefully, this will help someone else. Please note that the solutions and the code snippets were discovered on various forum posts by several developers. I give them full credit.

1) Fix the bug in Jetty that breaks the requests. (the bug is present at time of writing) Add custom servlet java classes that will supply the workaround. There are two classes HttpIfModifiedSinceFix and HttpModifiedSinceRequestWrapper. Add those servlet classes to the web.xml. (obviously put those classes in your own package and not the one shown below)

package com.bizznetworxonline.gaeworkaround;

import java.io.IOException;
import java.util.logging.Logger;

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;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 * Replaces the If-Modified-Since header until Google App Engine bug 8415 is 
 * resolved.
 * @see http://code.google.com/p/googleappengine/issues/detail?id=8415
 * 
 * @author Derek Berube, Wildstar Technologies
 *
 */
public class HttpIfModifiedSinceFix implements Filter {
  private static final String _CLASS = HttpIfModifiedSinceFix.class.getName();
  private static final Logger logger = Logger.getLogger(_CLASS);

  /**
   *  Called by the web container to indicate to a filter that it is being 
   *  taken out of service.
   */
  @Override
  public void destroy() {
    logger.entering(_CLASS,"destroy()");
    logger.exiting(_CLASS,"destroy()");
  }

  /**
   * The <code>doFilter</code> method of the Filter is called by the container 
   * each time a request/response pair is passed through the chain due to a 
   * client request for a resource at the end of the chain.
   */
  @Override
  public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {
    logger.entering(_CLASS,
        "doFilter(ServletRequest,ServletResponse,FilterChain)",
        new Object[] {request,response,chain});
    HttpServletRequest httpRequest=null;
    HttpServletRequestWrapper requestWrapper=null;
    
    httpRequest=(HttpServletRequest) request;
    requestWrapper=new HttpModifiedSinceRequestWrapper(httpRequest);
    chain.doFilter(requestWrapper, response);
    
    logger.exiting(_CLASS,
        "doFilter(ServletRequest,ServletResponse,FilterChain)");
  }

  /**
   * Called by the web container to indicate to a filter that it is being 
   * placed into service.
   */
  @Override
  public void init(FilterConfig config) throws ServletException {
    logger.entering(_CLASS,"init(FilterConfig)",config);
    logger.exiting(_CLASS,"init(FilterConfig)");
  } 
}

package com.bizznetworxonline.gaeworkaround;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Logger;

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

/**
 * Wraps the ServletRequest to remove the "If-Modified-Since" http header
 * @author Derek Berube, WildstarTechnologies, LLC.
 *
 */
public class HttpModifiedSinceRequestWrapper extends HttpServletRequestWrapper {
  private static final String _CLASS = 
      HttpModifiedSinceRequestWrapper.class.getName();
  private static final Logger logger = Logger.getLogger(_CLASS);
  /**
   * @param request
   */
  public HttpModifiedSinceRequestWrapper(HttpServletRequest request) {
    super(request);
    logger.entering(_CLASS,"HttpModifiedSinceRequestWrapper");
    logger.exiting(_CLASS,"HttpModifiedSinceRequestWrapper");
  } 

  @Override
  /**
   * Returns the header provided it is not the "If-Modified-Since" header.
   */
   public String getHeader(String name) {
    logger.entering(_CLASS,"getHeader(String)",name);
    String header=null;
    if (!"If-Modified-Since".equals(name)) {
      header=super.getHeader(name);
    } // END if (!"If-Modified-Since".equals(name))
    logger.exiting(_CLASS,"getHeader(String)",header);
    return header;
   }

   @SuppressWarnings("rawtypes")
   @Override
   /**
    * Returns headers stripping out the "If-Modified-Since" header if
    * present.
    */
   public Enumeration getHeaderNames() {
     logger.entering(_CLASS,"getHeaderNames()");
     Enumeration headerNames=null;
     Enumeration<?> enu=null;    
     List<String> names;
     String name=null;
     
     names=new ArrayList<String>();
     enu=super.getHeaderNames();
     
     while (enu.hasMoreElements()) {
       name = enu.nextElement().toString();
       if (!"If-Modified-Since".equals(name)) {
         names.add(name);
       } // END if (!"If-Modified-Since".equals(name))
     } // END while (enu.hasMoreElements())
     headerNames=Collections.enumeration(names);
     logger.exiting(_CLASS,"getHeaderNames()",headerNames);
     return headerNames;
   }
}
<filter>
      <display-name>AppEngine Bug 8145 Work Around</display-name>
      <description>
      Suppresses the If Modified Since header until GAE bug 8145 is fixed.
      </description>
      <filter-name>GAEBug8145WorkAround</filter-name>
      <filter-class>com.bizznetworxonline.gaeworkaround.HttpIfModifiedSinceFix</filter-class>
   </filter>
<filter-mapping>
      <filter-name>GAEBug8145WorkAround</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>

2) Add JSF 2.1 library to the project and include in the web.xml.

<!-- ***** Designate state saving. *****  -->
   <context-param>
      <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
      <param-value>client</param-value>
   </context-param>
   <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Production</param-value>
  </context-param>
<!-- Disable use of threading for single-threaded environments such as
        the Google AppEngine. -->
   <context-param>
      <param-name>com.sun.faces.enableThreading</param-name>
      <param-value>false</param-value>
   </context-param>
   <!-- ***** Load the JavaServer Faces Servlet ***** -->
   <servlet>
      <servlet-name>Faces Servlet</servlet-name>
      <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
      <servlet-name>Faces Servlet</servlet-name>
      <url-pattern>/faces/*</url-pattern>
      <url-pattern>*.jsf</url-pattern>
      <url-pattern>*.faces</url-pattern>
      <url-pattern>*.xhtml</url-pattern>
   </servlet-mapping>

3) There are context lookups in the current version of JSF that will implode on GAE. You need to remove those and the only way to do so is compile your own version of the WebConfiguration class into your project. You can use this one:

https://dl.dropboxusercontent.com/u/4179337/WebConfiguration.zip

4) Add the JBOSS Expression Language library. Why you ask??? This is because Google App Engine will always load the base libraries provided with AppEngine LAST. That is correct. They are loaded LAST. So, the libraries in your application will be overridden by the AppEngine versions.  Unfortunately, the libraries that come with AppEngine are old as dirt and you will be getting version 1.0 of the EL library. Yuck. The choice here is to switch to the JBoss version since that one will not be overridden. Doing this step allows for more advanced EL operations. It comes in handy especially for JSF. So, add the jboss-el.jar that comes with Seam and add the expression handler to the web.xml.

https://dl.dropboxusercontent.com/u/4179337/jboss-el.zip

<context-param>
    <param-name>com.sun.faces.expressionFactory</param-name>
    <param-value>org.jboss.el.ExpressionFactoryImpl</param-value>
  </context-param>

5) Add the EclipseLink library and modify the persistence.xml to be used with Google Cloud SQL. Remember that the Google App Engine plugin for eclipse will pass the mysql parameters configured in the plugin to your local mysql connection. You will need to setup a Google Cloud SQL instance and have that url configured in the persistence.xml. EclipseLink also needs to know the platform of the database. Don't forget to also drop the mysql jar file in the lib directory of the Google App Engine SDK in your local dev environment or else you will get a class not found exception when the mysql drivers are being loaded.

<property name="javax.persistence.jdbc.driver" value="com.google.appengine.api.rdbms.AppEngineDriver" />
        <property name="javax.persistence.jdbc.url" value="%JDBC_URL_OF_CLOUD_SQL_INSTANCE%" />
            <property name="eclipselink.target-database" value="MySQL"/>
            <property name="eclipselink.platform.class.name" value="org.eclipse.persistence.platform.database.MySQLPlatform"/>

That should be it. You can always drop a comment if you have problems setting this up. Good Luck.

No comments:

Post a Comment