如何设置带有JSF 2.2,JPA 2.0和依赖注入功能的Google AppEngine Web应用程序? [英] How do I set up a Google AppEngine webapp with JSF 2.2, JPA 2.0 and Dependency Injection features?

查看:145
本文介绍了如何设置带有JSF 2.2,JPA 2.0和依赖注入功能的Google AppEngine Web应用程序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

几周前,我被要求创建一个Web应用程序,以便在Google云端平台上运行(在输入此帖子时,SDK v1.9.48)。在配置完AppEngine设置(创建帐户,项目,云数据库,源代码库等等)之后,我准备使用GAE Eclipse插件开发我的第一个Web应用程序。



当我发现GAE只会默认支持JSP和servlet时,我感到非常失望。

然后我说:愿上帝帮助我又回到了J2EE的石器时代?我已经习惯了用于UI的JSF和(C)DI,我将如何将这3个J2EE标准集成到GAE webapp中,并使其顺利运行(如果可能的话) )?:
$ b

  • JSF 2.x

  • (C)DI 1.x

  • JPA 2.x



只要继续阅读这篇文章,你就会知道如何!那么,我决定不要轻易放弃,而是进入问题的解决方案。经过几周的艰苦研究和试错编码,我找到了解决这个问题的办法。



在开始这篇文章之前,我会给你一些很棒的这些资源可以帮助您将这些内容融合在一起:


  1. 配置JSF 2.2以使用Eclipse在Google App Engine上运行
  2. 会话数据丢失错误的解决方法
  3. 我使用的图书馆列表(其中至少有一半来自GAE SDK)

框架:


  1. 逸anucleus 3.1.1(JPA 2.0)

  2. Oracle Mojarra 2.2.4(JSF 2.2)。

  3. Google Guice 4.0(DI 1.0)


  4. 这是我如何运作的:

    最重要的配置是在web.xml中。 JSF初始化必须先运行:我发现 com.sun.faces.config.ConfigureListener 负责该步骤,它总是看起来像对于 FacesServlet 声明。由于JSF请求 MUST 由Guice通过 FacesHttpServlet 封装器(我稍后会发布)来启用DI,因此: p>

    我声明了 FacesServlet WITHOUT < servlet-mapping> s(我通过试错法编码得出了这一步)。



    它只是声明要初始化 FacesContextFactory 。这是web.xml的MUST-HAVE结构:

     <?xml version =1.0encoding =utf -8\" >?; 
    < web-app xmlns:xsi =http://www.w3.org/2001/XMLSchema-instance
    version =2.5xmlns =http://java.sun。 com / xml / ns / javaeexmlns:web =http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd
    xsi:schemaLocation =http:// java。 sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\">
    < display-name> BrochureForce< / display-name>

    < description>配置为在Google AppEngine上运行的采购订单生成器。< / description>

    < context-param>
    < description>项目阶段(开发或生产)< / description>
    < param-name> javax.faces.PROJECT_STAGE< / param-name>
    <参数值>开发< /参数值>
    < / context-param>

    < context-param>
    < description>
    指定客户端状态保存,因为GAE不处理
    服务器端(JSF默认)状态管理。
    < / description>
    < param-name> javax.faces.STATE_SAVING_METHOD< / param-name>
    < param-value>客户端< /参数值>
    < / context-param>

    < context-param>
    < description>将JSF页面的默认后缀设置为.xhtml< / description>
    < param-name> javax.faces.DEFAULT_SUFFIX< / param-name>
    < / param-value> .xhtml< / param-value>
    < / context-param>

    < context-param>
    < description>
    启用时,运行时初始化和默认的ResourceHandler
    实现将使用线程来执行其功能。如果不需要线程,则将此
    值设置为false(如单线程
    环境(例如Google AppEngine))。
    请注意,如果禁用此选项,则当ProjectStage进行开发时,ResourceHandler不会
    获取新版本的资源。
    < / description>
    < param-name> com.sun.faces.enableThreading< / param-name>
    < param-value> false< /参数值>
    < / context-param>

    < context-param>
    < description>允许依赖注入到ManagedBeans< / description>
    < param-name> com.sun.faces.injectionProvider< / param-name>
    < param-value> mypackage.jsf.JsfInjectionProvider< / param-value>
    < / context-param>

    < context-param>
    < description>指定默认的JBoss表达式语言< / description>
    < param-name> com.sun.faces.expressionFactory< / param-name>
    < param-value> org.jboss.el.E​​xpressionFactoryImpl< / param-value>
    < / context-param>

    <! - JSF INITIALIZATION GOES FIRST !! - >
    < servlet>
    < description>
    JSF 2 Servlet。这个servlet没有定义servlet映射,因为
    在这里被声明为了强制FacesFactory正确加载
    ,这样这个servlet的一个实例可以被注入到Guice使用的FacesHttpServlet
    中为JSF请求提供服务并同时作为注入提供者。
    此外,load-on-startup属性设置为0来告诉Jetty
    这个servlet必须首先加载。
    < / description>
    < servlet-name> JSF Servlet< / servlet-name>
    < servlet-class> javax.faces.webapp.FacesServlet< / servlet-class>
    0< / load-on-startup>
    < / servlet>
    < listener>
    < description> JSF初始化。< / description>
    < listener-class> com.sun.faces.config.ConfigureListener< / listener-class>
    < / listener>
    <! - JSF INITIALIZATION GOES FIRST! - >

    < listener>
    < description>持续引擎初始化和关闭。< / description>
    < listener-class> mypackage.listener.PersistenceManagerSetupListener< / listener-class>
    < / listener>

    <! - *****指定三十(30)分钟的会话超时。 ***** - >
    < session-config>
    < session-timeout> 30< / session-timeout>
    < / session-config>

    < welcome-file-list>
    < welcome-file> index.jsf< / welcome-file>
    < welcome-file> index.xhtml< / welcome-file>
    < / welcome-file-list>
    <! - ***************************************** *********** - >
    <! - DI API初始化(Google Guice实现)。 - >
    <! - ***************************************** *********** - >
    < filter>
    < description>启用DI的Google Guice过滤器。< / description>
    < filter-name> GuiceFilter< / filter-name>
    < filter-class> com.google.inject.servlet.GuiceFilter< / filter-class>
    < / filter>
    < filter-mapping>
    < filter-name> GuiceFilter< / filter-name>
    < url-pattern> / *< / url-pattern>
    < / filter-mapping>
    < listener>
    < description>
    这个监听器初始化Guice注入器,并将JSF Servlet
    封装到一个HttpServlet中,以便通过Guice过滤器提供JSF请求。
    < / description>
    < listener-class> mypackage.listener.GuiceListener< / listener-class>
    < / listener>
    <! - ***************************************** *********** - >
    < / web-app>

    其次,我没有试图将托管的bean实例注入到另一个实例中。相反,我向Bean中注入了一个绑定的业务逻辑实例(换句话说,模拟EJB行为)。这就是我所做的:


    1. 我为 @BindingAnnotation 定义了业务逻辑实现:

        import static java.lang.annotation.ElementType.TYPE; 
      import static java.lang.annotation.RetentionPolicy.RUNTIME;
      import java.lang.annotation.Documented;
      import java.lang.annotation.Retention;
      import java.lang.annotation.Target;
      import com.google.inject.BindingAnnotation;

      @Documented
      @BindingAnnotation
      @Retention(RUNTIME)
      @Target({TYPE})
      public @interface BusinessLogic {}

    2. 我定义了一个业务逻辑接口及其实现,并用 @BusinessLogic 注释(这是一个登录页面的示例,字段为:访问号码,源IP和时间戳):

        import java.util.List; 
      导入mypackage.annotation.BusinessLogic;
      导入mypackage.dataaccess.entity.Visit;

      @BusinessLogic
      public interface VisitsHandler {
      public void insertVisit();
      public List< Visit> getPageVisits();

      // Propiedades
      public String getCurrentVisit();
      public void setCurrentVisit(String currentVisit);

      code

      $ b $ p

      及其实现:

        import java.util.ArrayList; 
      import java.util.Date;
      import java.util.List;

      导入mypackage.annotation.BusinessLogic;
      导入mypackage.jsf.logic.VisitsHandler;
      导入mypackage.dataaccess.PersistenceManager;
      导入mypackage.dataaccess.Queries;
      导入mypackage.dataaccess.entity.Visit;

      @BusinessLogic
      public class VisitsHandlerImpl实现VisitsHandler {
      private String currentVisit;

      public void insertVisit(){
      PersistenceManager pMgr = PersistenceManager.getInstance();
      访问newVisit = new Visit();
      newVisit.setUserIp(127.0.0.1);
      newVisit.setTimestamp(new Date(System.currentTimeMillis()));
      pMgr.insert(newVisit);
      pMgr = null; //解引用单例实例。
      this.currentVisit = newVisit.toString();
      }

      @SuppressWarnings(rawtypes)
      public List< Visit> getPageVisits(){
      PersistenceManager pMgr = PersistenceManager.getInstance();
      列表<访问> visitsList = new ArrayList< Visit>();
      列表访问= pMgr.executeJpqlQuery(Queries.JPQL_VISITS);
      for(Object v:visits){
      visitsList.add((Visit)v);
      }
      pMgr = null; //解引用单例实例。
      返回visitsList;

      $ b / **
      * @return currentVisit
      * /
      public String getCurrentVisit(){
      return currentVisit;
      }
      $ b / **
      * @param currentVisit
      * currentVisit设置
      * /
      public void setCurrentVisit(String currentVisit) {
      this.currentVisit = currentVisit;


      code $


      为了避免业务逻辑对象重新实现,我定义了DI绑定的单个实例:

        import mypackage.jsf.logic.VisitsHandler; 
      导入mypackage.jsf.logic.impl.VisitsHandlerImpl;
      interface InjectorConstants {

      // FacesServlet的Url模式,因为它将在web.xml中定义
      static String [] JSF_SERVLET_URL_PATTERNS = new String [] {* .jsf ,* .xhtml};

      //商业逻辑对象。
      static Class< VisitsHandler> VISITS_HANDLER = VisitsHandler.class;
      static VisitsHandler VISITS_HANDLER_IMPL = new VisitsHandlerImpl();
      }

      现在,带有对象绑定的Guice模块:

        import javax.faces.webapp.FacesServlet; 
      import javax.inject.Singleton;

      导入mypackage.cdi.annotation.ViewScoped;
      导入mypackage.cdi.annotation.ViewScopedImpl;
      import mypackage.cdi.listener.PostConstructTypeListener;
      导入mypackage.jsf.FacesHttpServlet;
      import com.google.inject.matcher.Matchers;
      import com.google.inject.servlet.ServletModule;
      $ b $ public class JSFModule extends ServletModule {
      private void businessLogicBindings(){
      bind(InjectorConstants.VISITS_HANDLER).toInstance(InjectorConstants.VISITS_HANDLER_IMPL);
      }

      private void systemBindings(){
      //为Guice注入的
      //对象添加对@PostConstruct注解的支持。
      bindListener(Matchers.any(),new PostConstructTypeListener(null));

      //绑定@ViewScoped作用域的自定义实现。
      bindScope(ViewScoped.class,new ViewScopedImpl());


      private void jsfBindings(){
      //定义并绑定FacesServlet作为单例对象
      //所以它可以注入到FacesHttpServlet的构造函数中。
      bind(FacesServlet.class).in(Singleton.class);

      // FacesHttpServlet提供的JSF模式。
      for(String urlPattern:InjectorConstants.JSF_SERVLET_URL_PATTERNS){
      serve(urlPattern).with(FacesHttpServlet.class);

      }

      @Override
      protected void configureServlets(){
      // Guice注入器绑定。
      this.systemBindings();
      this.businessLogicBindings();
      this.jsfBindings();


      $ / code $ / pre
      $ b $ businessLogicBindings()方法将业务逻辑接口与实现实例相关联。另一方面,你可以在这行看到: serve(urlPattern).with(FacesHttpServlet.class); ,Guice会将JSF请求重新路由到HttpServlet包装器 FacesServlet 实例:
      $ b

        import java.io.IOException; 
      import javax.faces.webapp.FacesServlet;
      import javax.inject.Inject;
      import javax.inject.Singleton;
      import javax.servlet.Servlet;
      import javax.servlet.ServletConfig;
      import javax.servlet.ServletException;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;

      @Singleton
      public class FacesHttpServlet extends HttpServlet {

      private static final long serialVersionUID = 1L;

      private final Servlet facesServlet;

      @Inject
      public FacesHttpServlet(FacesServlet facesServlet){
      this.facesServlet = facesServlet;
      }

      @Override
      public void init(ServletConfig config)throws ServletException {
      this.facesServlet.init(config); (


      @Override
      public ServletConfig getServletConfig(){
      return this.facesServlet.getServletConfig();
      }

      @Override
      public String getServletInfo(){
      return this.facesServlet.getServletInfo();
      }

      @Override
      public void destroy(){
      super.destroy();
      this.facesServlet.destroy();

      $ b @Override
      public void service(ServletRequest req,ServletResponse resp)throws ServletException,IOException {
      HttpServletRequest httpReq =(HttpServletRequest)req;
      String reqUrl = httpReq.getRequestURL()。toString();
      //破解重定向索引页面。如果请求URL中没有明确指定
      ///index.[xhtml|jsf],那么就会抛出一个错误。
      if(reqUrl.toLowerCase()。endsWith(index.xhtml)){
      ((HttpServletResponse)resp).sendRedirect(reqUrl.replace(index.xhtml,index.jsf ));
      } else {
      this.facesServlet.service(req,resp);





      $ b现在,初始化的监听器注入器:

        import java.util.HashMap; 
      import mypackage.cdi.JSFModule;
      导入mypackage.cdi.JsfInjectionProvider;
      import com.google.inject.AbstractModule;
      import com.google.inject.Guice;
      import com.google.inject.Injector;
      import com.google.inject.servlet.GuiceServletContextListener;

      公共类GuiceListener扩展了GuiceServletContextListener {
      protected AbstractModule模块;
      保护静态喷油器喷油器;
      private static HashMap< String,Object> instancesMap;

      public GuiceListener(){
      // Bean实例列表确保我们注入一个唯一的bean实例。
      instancesMap = new HashMap<>();

      //创建注入器。
      injector = Guice.createInjector(new JSFModule());
      }

      @Override
      public Injector getInjector(){
      return injector;
      }

      / **
      *给定一个类,生成一个注入的实例。 API调用在内部需要
      *时非常有用。
      * /
      public static< T> T getInstance(Class< T> type){
      return injector.getInstance(type);
      }

      / **
      *给定一个可注入的实例,注入它的依赖并确保
      *只注入一个。
      * /
      public static void injectMembers(Object instance){
      Object obj = null;
      if(JsfInjectionProvider.isBusinessLogicObject(obj)){
      String instanceClassName = instance.getClass()。getName();
      Object mappedInstance = instancesMap.get(instanceClassName);
      if(mappedInstance == null){
      //这是一个新的bean实例。它存储在bean映射
      //中,以便能够重用它。
      instancesMap.put(instanceClassName,instance);
      obj = instance;
      } else {
      //已经有一个bean实例。让我们重复使用它!
      obj = mappedInstance;
      }
      } else {//它应该是一个托管bean。
      obj = instance;
      }
      injector.injectMembers(obj);


      $ / code>

      最后但并非最不重要的是,Mojarra必须注册我们的DI作为DI提供者的实现(参见< context-param> com.sun.faces.injectionProvider 值):

        import javax.faces.bean.ManagedBean; 
      导入mypackage.cdi.annotation.BusinessLogic;
      导入mypackage.cdi.listener.GuiceListener;
      import com.sun.faces.spi.InjectionProviderException;
      import com.sun.faces.vendor.WebContainerInjectionProvider;

      public class JsfInjectionProvider extends WebContainerInjectionProvider {
      @Override
      public void inject(Object obj)throws InjectionProviderException {
      if(isManagedBean(obj)|| isBusinessLogicObject(obj) ){
      GuiceListener.injectMembers(obj);
      }
      }

      / **
      *作为一个任意的选择,这里的选择是只注入
      * {@code @ManagedBean}实例,以便其他类 - 不是由
      *写的 - 不会被注入。这个选择可以改变。
      *
      * @param obj
      *一个JSF bean实例(用@ManagedBean注释)。
      * @return
      * /
      private boolean isManagedBean(Object obj){
      return obj!= null&& obj.getClass()。getAnnotation(ManagedBean.class)!= null;
      }

      public static boolean isBusinessLogicObject(Object obj){
      return obj!= null&& obj.getClass()。getAnnotation(BusinessLogic.class)!= null;




      $ b $ p
      $ b

      完全可以工作(但是省略了JPA部分,在这一点上不相关):
      ExampleBean

        import java.io.Serializable; 
      import java.util.List;

      import javax.annotation.PostConstruct;
      import javax.faces.bean.ManagedBean;
      import javax.inject.Inject;

      导入mypackage.jsf.logic.VisitsHandler;
      导入mypackage.dataaccess.entity.Visit;

      @ManagedBean(name =jsfbExample)
      public class ExampleBean实现Serializable {

      private static final long serialVersionUID = 1L;

      @Inject
      私人VisitsHandler visitsHandler;

      @PostConstruct
      public void init(){
      System.out.println(ExampleBean - Injection works!visitsHandler =+ visitsHandler); // 有用。
      }

      / **
      *用参数测试EL引擎处理的方法。
      * @param param
      * @return
      * /
      public void insertVisit(){
      this.visitsHandler.insertVisit();
      }

      public List< Visit> getPageVisits(){
      返回this.visitsHandler.getPageVisits();


      $ b / **
      * @return currentVisit
      * /
      public String getCurrentVisit(){
      return this.visitsHandler.getCurrentVisit();
      }
      $ b / **
      * @param currentVisit
      * currentVisit设置
      * /
      public void setCurrentVisit(String currentVisit) {
      this.visitsHandler.setCurrentVisit(currentVisit);
      }
      }

      现在,您可以创建一个* .xhtml文件为你的inxdex并把这个测试代码放在上面:

       <!DOCTYPE html 
      PUBLIC - // W3C / / DTD XHTML 1.0 Strict // EN
      http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">
      xmlns:f =http:// java .sun.com / jsf / core
      xmlns:h =http://java.sun.com/jsf/html
      xmlns:ui =http://java.sun.com / JSF /小面>
      < h:head id =head>
      < meta http-equiv =Content-Typecontent =text / html; charset = UTF-8/>
      < title>欢迎使用Google AppEngine上的JSF 2.1!< / title>
      < / h:头>
      < h:body>
      < h:form>
      < h:outputText id =lastVisitvalue =#{jsfbExample.currentVisit}/>< br />
      < h:commandButton value =新访问!
      actionListener =#{jsfbExample.insertVisit()}>
      < / h:commandButton>
      < h:commandButton value =最后插入的访问!>
      < / h:commandButton>
      < h:panelGrid id =pageVisitsList>
      < c:forEach var =visititems =#{jsfbExample.pageVisits}>
      < h:outputText value =#{visit.toString()}/>
      < / c:forEach>
      < / h:panelGrid>
      < / h:表格>
      < / h:body>
      < / html>

      JPA特性比较容易,因为它的配置既不依赖于JSF也不依赖于DI。
      PersistenceManagerSetupListener


        package mypackage.listener; 

      import javax.servlet.ServletContextEvent;
      import javax.servlet.ServletContextListener;
      导入mypackage.dataaccess.PersistenceManager;
      导入mypackage.utils.StringMap;

      public class PersistenceManagerSetupListener实现ServletContextListener {

      @Override
      public void contextInitialized(ServletContextEvent servletContextInitEvt){

      //这只是一个封装在HashMap上< String,String>
      StringMap initProperties = new StringMap();

      //检查系统属性以确定我们是否正在云
      上运行//并且相应地设置JDBC驱动程序。
      String platform = System.getProperty(com.google.appengine.runtime.version)。toLowerCase()
      .contains(google app engine)? \"cloud\" : \"dev\";
      initProperties.put(\"datanucleus.ConnectionURL\", System.getProperty(platform + \".db.url\"));
      initProperties.put(\"datanucleus.ConnectionDriverName\", System.getProperty(platform + \".db.driver\"));
      initProperties.put(\"datanucleus.ConnectionUserName\", System.getProperty(platform + \".db.user\"));
      initProperties.put(\"datanucleus.ConnectionPassword\", System.getProperty(platform + \".db.password\"));
      // I implemented password encryption. See Datanucleus’ \"ConnectionEncryptionProvider\" interface documentation.
      initProperties.put(\"datanucleus.ConnectionPasswordDecrypter\",
      System.getProperty(platform + \".db.encryptionProvider\"));

      // ***********************************************************************************************************
      // THESE 2 ARE A MUST-HAVE!!!
      // ***********************************************************************************************************
      initProperties.put(\"datanucleus.identifier.case\", System.getProperty(\"persistencemanager.identifier.case\"));
      initProperties.put(\"datanucleus.storeManagerType\", System.getProperty(\"persistencemanager.storeManagerType\"));
      // ***********************************************************************************************************

      initProperties.put(\"datanucleus.NontransactionalRead\",
      System.getProperty(\"persistencemanager.NontransactionalRead\"));
      initProperties.put(\"datanucleus.NontransactionalRead\",
      System.getProperty(\"persistencemanager.NontransactionalRead\"));
      initProperties.put(\"datanucleus.NontransactionalWrite\",
      System.getProperty(\"persistencemanager.NontransactionalWrite\"));
      initProperties.put(\"datanucleus.singletonEMFForName\",
      System.getProperty(\"persistencemanager.singletonEMFForName\"));
      initProperties.put(\"javax.persistence.query.timeout\", System.getProperty(\"persistencemanager.query.timeout\"));
      initProperties.put(\"datanucleus.datastoreWriteTimeout\",
      System.getProperty(\"persistencemanager.datastoreWriteTimeout\"));


      // Initialize persistence engine.
      PersistenceManager.initialize(initProperties);
      }

      @Override
      public void contextDestroyed(ServletContextEvent servletContextDestroyedEvt) {
      PersistenceManager.shutdown();
      }
      }

      All the persistence init properties are defined in app-engine.xml. Its basic structure:

                  <appengine-web-app ...> 
      <application>cloud-project-id</application>
      <version>1</version>
      <threadsafe>true</threadsafe>
      <system-properties>
      <!-- Cloud platform properties (their name starts with \"cloud\") -->
      <property name=\"cloud.db.url\"
      value=\"jdbc:google:mysql://(cloud-connection-name)/(db-name)\" />
      <property name=\"cloud.db.driver\"
      value=\"com.google.appengine.api.rdbms.AppEngineDriver\" />
      <!-- ... -->
      <!-- Dev platform properties (their name starts with \"dev\") -->
      <property name=\"dev.db.url\" value=\"jdbc:mysql://(db-server):(db-port)/(db-name)\" />
      <property name=\"dev.db.driver\" value=\"com.mysql.jdbc.Driver\" />
      <!-- ... -->
      <!-- Datanucleus properties -->
      <!-- *********************************************** -->
      <!-- THESE 2 ARE A MUST-HAVE!!! Others are optional -->
      <!-- *********************************************** -->
      <property name=\"persistencemanager.storeManagerType\" value=\"rdbms\" />

      <!-- This means that all DB identifiers MUST be defined in lowercase. - >
      <property name=\"persistencemanager.identifier.case\" value=\"LowerCase\" />
      <!-- *********************************************** -->
      <!-- ... -->
      </system-properties>
      <sessions-enabled>true</sessions-enabled>
      <async-session-persistence enabled=\"false\" />
      <static-files>
      <exclude path=\"/**.xhtml\" />
      </static-files>
      </appengine-web-app>

      You must define at least one persistence unit (in \"persistence.xml\"):

                  <?xml version=\"1.0\" encoding=\"UTF-8\" ?> 
      <persistence xmlns=\"http://java.sun.com/xml/ns/persistence\"
      xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
      xsi:schemaLocation=\"http://java.sun.com/xml/ns/persistence
      http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd\"
      version=\"1.0\">

      <persistence-unit name=\"MyPersistenceUnit\">
      <!-- DATANUCLEUS’ JPA 2.0 PERSISTENCE PROVIDER CLASS -->
      <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>

      <!-- ENTITY CLASSES -->
      <class>mypackage.dataaccess.entity.Visit</class>

      <!-- DON’T PROCESS UNLISTED CLASSES AS ENTITY CLASSES. - >
      <exclude-unlisted-classes>true</exclude-unlisted-classes>
      </persistence-unit>
      </persistence>

      and some initialize and shutdown methods in your persistence manager object(s) to create and destroy the EntityManagerFactory and the EntityManager(s). Something like this:

                      public static void initialize(Map properties) { 
      if (!isInitialized) {
      if (properties == null) {
      emfInstance = Persistence.createEntityManagerFactory(\"MyPersistenceUnit\");
      } else {
      emfInstance = Persistence.createEntityManagerFactory(\"MyPersistenceUnit\", properties);
      }
      emInstance = emfInstance.createEntityManager();
      isInitialized = true;
      }
      }

      public static void shutdown() {
      try {
      emInstance.close();
      } catch (Exception e) {}
      try {
      emfInstance.close();
      } catch (Exception e) {}
      }

      The \"Visit\" class is just an Entity class which maps the 3 fields (Number of visit, source IP and timestamp) and it’s registered in the \"persistence.xml\" file.



      I wrote this post as a tutorial that shows, step-by-step, how I managed to run these technologies on GAE (SDK 1.9.48 by the time I typed these lines in). It’s taken me weeks of researching and trial-error coding, and I expect with this guide to help other Java programmers not to go through that mess as I did.



      Hope this guide can help others to create great J2EE apps in GAE.


      A couple of weeks ago I was asked to create a web application to run on Google Cloud Platform (SDK v1.9.48 by the time this post was typed in). After configuring AppEngine settings (creating account, project, cloud database, source code repository, etc, etc, etc...), I was ready to develop my first webapp using GAE Eclipse Plugin.

      It was huge my dissapointment when I found out that GAE only brings along by default support for JSP and servlets.

      Then I said: "May God help me! Back to J2EE's stone age again? I'm used to JSF and (C)DI for the UI. How am I going to integrate in a GAE webapp these 3 J2EE standards and make it run smoothly (if such a thing is possible)?":

      • JSF 2.x
      • (C)DI 1.x
      • JPA 2.x

      Just keep reading this post and you'll know how!

      解决方案

      Well, I decided not to give up so easily and get into the problem. After a couple of weeks of hard researching and trial-error coding, I found the solution to this mess.

      Before beginning with the post, I'll give you some great resources that can help you to pull this together:

      1. Configuring JSF 2.2 to run on the Google App Engine Using Eclipse
      2. A workaround for a session data loss bug
      3. List of libraries I used (at least half of them come bundled in GAE SDK)

      Frameworks:

      1. Datanucleus 3.1.1 (JPA 2.0)
      2. Oracle Mojarra 2.2.4 (JSF 2.2).
      3. Google Guice 4.0 (DI 1.0)

      This is how I got it to work:

      The most important configuration is in web.xml. The JSF initialization MUST RUN FIRST: I found out that com.sun.faces.config.ConfigureListener is in charge of that step and it always looks for the FacesServlet declaration. Since JSF requests MUST be served by Guice with a FacesHttpServlet wrapper (which I'll post later) in order to enable DI, then:

      I declared the FacesServlet WITHOUT <servlet-mapping>s (I figured out that step by trial-error coding).

      It's only declared to initialize the FacesContextFactory. This is the MUST-HAVE structure of the web.xml:

                  <?xml version="1.0" encoding="utf-8"?>
                  <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                      version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
                      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
                      <display-name>BrochureForce</display-name>
      
                      <description>Purchase orders generator configured to run on the Google AppEngine.</description>
      
                      <context-param>
                          <description>Project stage (Development or Production)</description>
                          <param-name>javax.faces.PROJECT_STAGE</param-name>
                          <param-value>Development</param-value>
                      </context-param>
      
                      <context-param>
                          <description>
                                      Designate client-side state saving, since GAE doesn't handle 
                                      server side (JSF default) state management.
                          </description>
                          <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
                          <param-value>client</param-value>
                      </context-param>
      
                      <context-param>
                          <description>Sets the default suffix for JSF pages to .xhtml</description>
                          <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
                          <param-value>.xhtml</param-value>
                      </context-param>
      
                      <context-param>
                          <description>
                                   When enabled, the runtime initialization and default ResourceHandler 
                                   implementation will use threads to perform their functions. Set this 
                                   value to false if threads aren't desired (as in the case of single-threaded
                                   environments such as the Google AppEngine).
                                   Note that when this option is disabled, the ResourceHandler will not 
                                   pick up new versions of resources when ProjectStage is development.
                              </description>
                          <param-name>com.sun.faces.enableThreading</param-name>
                          <param-value>false</param-value>
                      </context-param>
      
                      <context-param>
                          <description>Allows dependency-injection into ManagedBeans</description>
                          <param-name>com.sun.faces.injectionProvider</param-name>
                          <param-value>mypackage.jsf.JsfInjectionProvider</param-value>
                      </context-param>
      
                      <context-param>
                          <description>Specify JBoss Expression Language Over Default</description>
                          <param-name>com.sun.faces.expressionFactory</param-name>
                          <param-value>org.jboss.el.ExpressionFactoryImpl</param-value>
                      </context-param>
      
                      <!-- JSF INITIALIZATION GOES FIRST!! -->
                      <servlet>
                          <description>
                                  JSF 2 Servlet. There's NO servlet-mapping defined for this servlet because
                                  it's declared here in order to enforce the FacesFactory to load properly
                                  so that an instance of this servlet can be injected in the FacesHttpServlet
                                  used by Guice to serve JSF requests and as injection provider at the same time.
                                  Furthermore, the "load-on-startup" property is set to "0" to tell Jetty
                                  that this servlet MUST be loaded first.
                          </description>
                          <servlet-name>JSF Servlet</servlet-name>
                          <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
                          <load-on-startup>0</load-on-startup>
                      </servlet>
                      <listener>
                          <description>JSF Initialization.</description>
                          <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
                      </listener>
                      <!-- JSF INITIALIZATION GOES FIRST!! -->
      
                      <listener>
                          <description>PERSISTENCE ENGINE INITIALIZATION AND SHUTDOWN.</description>
                          <listener-class>mypackage.listener.PersistenceManagerSetupListener</listener-class>
                      </listener>
      
                      <!-- ***** Specify session timeout of thirty (30) minutes. ***** -->
                      <session-config>
                          <session-timeout>30</session-timeout>
                      </session-config>
      
                      <welcome-file-list>
                          <welcome-file>index.jsf</welcome-file>
                          <welcome-file>index.xhtml</welcome-file>
                      </welcome-file-list>
                      <!-- **************************************************** -->
                      <!-- DI API initialization (Google Guice Implementation). -->
                      <!-- **************************************************** -->
                      <filter>
                          <description>Google Guice filter which enables DI.</description>
                          <filter-name>GuiceFilter</filter-name>
                          <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
                      </filter>
                      <filter-mapping>
                          <filter-name>GuiceFilter</filter-name>
                          <url-pattern>/*</url-pattern>
                      </filter-mapping>
                      <listener>
                          <description>
                                      This listener initializes the Guice injector and wraps the JSF Servlet
                                      into a HttpServlet in order to serve JSF requests via Guice Filter.
                              </description>
                          <listener-class>mypackage.listener.GuiceListener</listener-class>
                      </listener>
                      <!-- **************************************************** -->
                  </web-app>
      

      Second, I'm not trying to inject a managed bean instance into another anymore. Instead, I'm injecting a bound business logic instance into the beans (in other words, emulating EJB behavior). This is what I did:

      1. I defined a @BindingAnnotation for the business logic implementation:

                import static java.lang.annotation.ElementType.TYPE;
                import static java.lang.annotation.RetentionPolicy.RUNTIME;
                import java.lang.annotation.Documented;
                import java.lang.annotation.Retention;
                import java.lang.annotation.Target;
                import com.google.inject.BindingAnnotation;
        
                @Documented
                @BindingAnnotation
                @Retention(RUNTIME)
                @Target({ TYPE })
                public @interface BusinessLogic {}
        

      2. I defined a business logic interface with its implementation and annotated both with the @BusinessLogic annotation (This is an example that registers a visit made to the page. The fields are: the visit number, the source IP and the timestamp):

                import java.util.List;
                import mypackage.annotation.BusinessLogic;
                import mypackage.dataaccess.entity.Visit;
        
                @BusinessLogic
                public interface VisitsHandler {
                    public void insertVisit();
                    public List<Visit> getPageVisits();
        
                    // Propiedades
                    public String getCurrentVisit();
                    public void setCurrentVisit(String currentVisit);
                }
        

      and its implementation:

                  import java.util.ArrayList;
                  import java.util.Date;
                  import java.util.List;
      
                  import mypackage.annotation.BusinessLogic;
                  import mypackage.jsf.logic.VisitsHandler;
                  import mypackage.dataaccess.PersistenceManager;
                  import mypackage.dataaccess.Queries;
                  import mypackage.dataaccess.entity.Visit;
      
                  @BusinessLogic
                  public class VisitsHandlerImpl implements VisitsHandler {
                      private String currentVisit;
      
                      public void insertVisit() {
                          PersistenceManager pMgr = PersistenceManager.getInstance();
                          Visit newVisit = new Visit();
                          newVisit.setUserIp("127.0.0.1");
                          newVisit.setTimestamp(new Date(System.currentTimeMillis()));
                          pMgr.insert(newVisit);
                          pMgr = null; // Dereference the singleton instance.
                          this.currentVisit = newVisit.toString();
                      }
      
                      @SuppressWarnings("rawtypes")
                      public List<Visit> getPageVisits() {
                          PersistenceManager pMgr = PersistenceManager.getInstance();
                          List<Visit> visitsList = new ArrayList<Visit>(); 
                          List visits = pMgr.executeJpqlQuery(Queries.JPQL_VISITS);
                          for (Object v : visits) {
                              visitsList.add((Visit) v);
                          }
                          pMgr = null; // Dereference the singleton instance.
                          return visitsList;
                      }
      
                      /**
                       * @return the currentVisit
                       */
                      public String getCurrentVisit() {
                          return currentVisit;
                      }
      
                      /**
                       * @param currentVisit
                       *            the currentVisit to set
                       */
                      public void setCurrentVisit(String currentVisit) {
                          this.currentVisit = currentVisit;
                      }   
                  }
      

      To avoid reinstantiation of the business logic objects, I defined a single instance for the DI binding:

                  import mypackage.jsf.logic.VisitsHandler;
                  import mypackage.jsf.logic.impl.VisitsHandlerImpl;
                  interface InjectorConstants {
      
                      // Url patterns for FacesServlet, as it would be defined in web.xml
                      static String[] JSF_SERVLET_URL_PATTERNS = new String[] { "*.jsf", "*.xhtml" };
      
                      // BUSINESS LOGIC OBJECTS.
                      static Class<VisitsHandler> VISITS_HANDLER = VisitsHandler.class;
                      static VisitsHandler VISITS_HANDLER_IMPL = new VisitsHandlerImpl();
                  }
      

      Now, the Guice module with the object bindings:

                  import javax.faces.webapp.FacesServlet;
                  import javax.inject.Singleton;
      
                  import mypackage.cdi.annotation.ViewScoped;
                  import mypackage.cdi.annotation.ViewScopedImpl;
                  import mypackage.cdi.listener.PostConstructTypeListener;
                  import mypackage.jsf.FacesHttpServlet;
                  import com.google.inject.matcher.Matchers;
                  import com.google.inject.servlet.ServletModule;
      
                  public class JSFModule extends ServletModule {
                      private void businessLogicBindings() {
                          bind(InjectorConstants.VISITS_HANDLER).toInstance(InjectorConstants.VISITS_HANDLER_IMPL);
                      }
      
                      private void systemBindings() {
                          // Add support for the @PostConstruct annotation for Guice-injected
                          // objects.
                          bindListener(Matchers.any(), new PostConstructTypeListener(null));
      
                          // Binding a custom implementation of "@ViewScoped" scope.
                          bindScope(ViewScoped.class, new ViewScopedImpl());
                      }
      
                      private void jsfBindings() {
                          // Define and bind FacesServlet as singleton object
                          // so it can be injected in FacesHttpServlet's constructor.
                          bind(FacesServlet.class).in(Singleton.class);
      
                          // JSF patterns to be served by FacesHttpServlet.
                          for (String urlPattern : InjectorConstants.JSF_SERVLET_URL_PATTERNS) {
                              serve(urlPattern).with(FacesHttpServlet.class);
                          }
                      }
      
                      @Override
                      protected void configureServlets() {
                          // Guice injector bindings.
                          this.systemBindings();
                          this.businessLogicBindings();
                          this.jsfBindings();
                      }
                  }
      

      The businessLogicBindings() method associates the business logic interface with the implementation instance. On the other hand, you can see on this line: serve(urlPattern).with(FacesHttpServlet.class);, Guice will reroute JSF requests to a HttpServlet wrapper with an injected FacesServlet instance:

                  import java.io.IOException;
                  import javax.faces.webapp.FacesServlet;
                  import javax.inject.Inject;
                  import javax.inject.Singleton;
                  import javax.servlet.Servlet;
                  import javax.servlet.ServletConfig;
                  import javax.servlet.ServletException;
                  import javax.servlet.ServletRequest;
                  import javax.servlet.ServletResponse;
                  import javax.servlet.http.HttpServlet;
                  import javax.servlet.http.HttpServletRequest;
                  import javax.servlet.http.HttpServletResponse;
      
                  @Singleton
                  public class FacesHttpServlet extends HttpServlet {
      
                      private static final long serialVersionUID = 1L;
      
                      private final Servlet facesServlet;
      
                      @Inject
                      public FacesHttpServlet(FacesServlet facesServlet) {
                          this.facesServlet = facesServlet;
                      }
      
                      @Override
                      public void init(ServletConfig config) throws ServletException {
                          this.facesServlet.init(config);
                      }
      
                      @Override
                      public ServletConfig getServletConfig() {
                          return this.facesServlet.getServletConfig();
                      }
      
                      @Override
                      public String getServletInfo() {
                          return this.facesServlet.getServletInfo();
                      }
      
                      @Override
                      public void destroy() {
                          super.destroy();
                          this.facesServlet.destroy();
                      }
      
                      @Override
                      public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException {
                          HttpServletRequest httpReq = (HttpServletRequest) req;
                          String reqUrl = httpReq.getRequestURL().toString();
                          // A hack to redirect the index page. It's been throwing an error if the
                          // "/index.[xhtml|jsf]" is not explicitly specified in the request URL.
                          if(reqUrl.toLowerCase().endsWith("index.xhtml")) {
                              ((HttpServletResponse) resp).sendRedirect(reqUrl.replace("index.xhtml", "index.jsf"));
                          } else {
                              this.facesServlet.service(req, resp);
                          }
                      }
                  }
      

      Now, the listener that initializes the injector:

                  import java.util.HashMap;
                  import mypackage.cdi.JSFModule;
                  import mypackage.cdi.JsfInjectionProvider;
                  import com.google.inject.AbstractModule;
                  import com.google.inject.Guice;
                  import com.google.inject.Injector;
                  import com.google.inject.servlet.GuiceServletContextListener;
      
                  public class GuiceListener extends GuiceServletContextListener {
                      protected AbstractModule module;
                      protected static Injector injector;
                      private static HashMap<String, Object> instancesMap;
      
                      public GuiceListener() {
                          // Bean instance list to ensure that we inject a unique bean instance.
                          instancesMap = new HashMap<>();
      
                          // Create the injector.
                          injector = Guice.createInjector(new JSFModule());
                      }
      
                      @Override
                      public Injector getInjector() {
                          return injector;
                      }
      
                      /**
                       * given a class, generates an injected instance. Useful when an API call is
                       * needed internally.
                       */
                      public static <T> T getInstance(Class<T> type) {
                          return injector.getInstance(type);
                      }
      
                      /**
                       * given an injectable instance, injects its dependencies and make sure to
                       * only inject one.
                       */
                      public static void injectMembers(Object instance) {
                          Object obj = null;
                          if (JsfInjectionProvider.isBusinessLogicObject(obj)) {
                              String instanceClassName = instance.getClass().getName();
                              Object mappedInstance = instancesMap.get(instanceClassName);
                              if (mappedInstance == null) {
                                  // It's a new bean instance. It's stored in the bean map
                                  // to be able to reuse it.
                                  instancesMap.put(instanceClassName, instance);
                                  obj = instance;
                              } else {
                                  // There's already a bean instance. Let's reuse it!.
                                  obj = mappedInstance;
                              }
                          } else { // it should be a managed bean.
                              obj = instance;
                          }
                          injector.injectMembers(obj);
                      }
                  }
      

      Last, but not least, Mojarra must register our DI implementation as its DI provider (see the <context-param> com.sun.faces.injectionProvider value):

                  import javax.faces.bean.ManagedBean;
                  import mypackage.cdi.annotation.BusinessLogic;
                  import mypackage.cdi.listener.GuiceListener;
                  import com.sun.faces.spi.InjectionProviderException;
                  import com.sun.faces.vendor.WebContainerInjectionProvider;
      
                  public class JsfInjectionProvider extends WebContainerInjectionProvider {
                      @Override
                      public void inject(Object obj) throws InjectionProviderException {
                          if (isManagedBean(obj) || isBusinessLogicObject(obj)) {
                              GuiceListener.injectMembers(obj);
                          }
                      }
      
                      /**
                       * As an arbitrary choice, the choice here is to inject only into
                       * {@code @ManagedBean} instances, so that other classes - not written by us
                       * - wouldn't be injected too. This choice could be altered.
                       * 
                       * @param obj
                       *            A JSF bean instance (annotated with @ManagedBean).
                       * @return
                       */
                      private boolean isManagedBean(Object obj) {
                          return obj != null && obj.getClass().getAnnotation(ManagedBean.class) != null;
                      }
      
                      public static boolean isBusinessLogicObject(Object obj) {
                          return obj != null && obj.getClass().getAnnotation(BusinessLogic.class) != null;
                      }
                  }
      

      All of this working altogether (yet ommitting the JPA part, which is not relevant at this point): ExampleBean:

                  import java.io.Serializable;
                  import java.util.List;
      
                  import javax.annotation.PostConstruct;
                  import javax.faces.bean.ManagedBean;
                  import javax.inject.Inject;
      
                  import mypackage.jsf.logic.VisitsHandler;
                  import mypackage.dataaccess.entity.Visit;
      
                  @ManagedBean(name="jsfbExample")
                  public class ExampleBean implements Serializable {
      
                      private static final long serialVersionUID = 1L;
      
                      @Inject
                      private VisitsHandler visitsHandler;
      
                      @PostConstruct
                      public void init() {
                          System.out.println("ExampleBean - Injection works! visitsHandler = " + visitsHandler); // It works.
                      }
      
                      /**
                       * Method to test EL engine processing with parameters. 
                       * @param param
                       * @return
                       */
                      public void insertVisit() {
                          this.visitsHandler.insertVisit();
                      }
      
                      public List<Visit> getPageVisits() {
                          return this.visitsHandler.getPageVisits();
                      }
      
      
                      /**
                       * @return the currentVisit
                       */
                      public String getCurrentVisit() {
                          return this.visitsHandler.getCurrentVisit();
                      }
      
                      /**
                       * @param currentVisit
                       *            the currentVisit to set
                       */
                      public void setCurrentVisit(String currentVisit) {
                          this.visitsHandler.setCurrentVisit(currentVisit);
                      }
                  }
      

      Now, you can create a *.xhtml file as your inxdex and put this testing code on it:

                  <!DOCTYPE html 
                           PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                           "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
                  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
                      xmlns:f="http://java.sun.com/jsf/core"
                      xmlns:h="http://java.sun.com/jsf/html"
                      xmlns:ui="http://java.sun.com/jsf/facelets">
                  <h:head id="head">
                      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
                      <title>Welcome to JSF 2.1 on the Google AppEngine!</title>
                  </h:head>
                  <h:body>
                          <h:form>
                              <h:outputText id="lastVisit" value="#{jsfbExample.currentVisit}" /><br/>
                              <h:commandButton value="New visit!"
                                  actionListener="#{jsfbExample.insertVisit()}">
                                  <f:ajax execute="@this" render="pageVisitsList" />
                              </h:commandButton>
                              <h:commandButton value="Last inserted visit!">
                                  <f:ajax execute="@this" render="lastVisit" />
                              </h:commandButton>
                              <h:panelGrid id="pageVisitsList">
                                  <c:forEach var="visit" items="#{jsfbExample.pageVisits}">
                                      <h:outputText value="#{visit.toString()}" />
                                  </c:forEach>
                              </h:panelGrid>
                          </h:form>
                  </h:body>
                  </html>
      

      JPA feature is easier since its configuration neither depends on JSF nor DI. PersistenceManagerSetupListener:

                  package mypackage.listener;
      
                  import javax.servlet.ServletContextEvent;
                  import javax.servlet.ServletContextListener;
                  import mypackage.dataaccess.PersistenceManager;
                  import mypackage.utils.StringMap;
      
                  public class PersistenceManagerSetupListener implements ServletContextListener {
      
                      @Override
                      public void contextInitialized(ServletContextEvent servletContextInitEvt) {
      
                        // This is only a wrapper over HashMap<String, String>
                          StringMap initProperties = new StringMap();
      
                          // Check the System properties to determine if we are running on cloud
                          // or not, and set up the JDBC driver accordingly.
                          String platform = System.getProperty("com.google.appengine.runtime.version").toLowerCase()
                                  .contains("google app engine") ? "cloud" : "dev";
                          initProperties.put("datanucleus.ConnectionURL", System.getProperty(platform + ".db.url"));
                          initProperties.put("datanucleus.ConnectionDriverName", System.getProperty(platform + ".db.driver"));
                          initProperties.put("datanucleus.ConnectionUserName", System.getProperty(platform + ".db.user"));
                          initProperties.put("datanucleus.ConnectionPassword", System.getProperty(platform + ".db.password"));
                          // I implemented password encryption. See Datanucleus' "ConnectionEncryptionProvider" interface documentation.
                        initProperties.put("datanucleus.ConnectionPasswordDecrypter",
                                  System.getProperty(platform + ".db.encryptionProvider"));
      
                          // ***********************************************************************************************************
                          // THESE 2 ARE A MUST-HAVE!!!
                          // ***********************************************************************************************************
                          initProperties.put("datanucleus.identifier.case", System.getProperty("persistencemanager.identifier.case"));
                          initProperties.put("datanucleus.storeManagerType", System.getProperty("persistencemanager.storeManagerType"));
                          // ***********************************************************************************************************
      
                          initProperties.put("datanucleus.NontransactionalRead",
                                  System.getProperty("persistencemanager.NontransactionalRead"));
                          initProperties.put("datanucleus.NontransactionalRead",
                                  System.getProperty("persistencemanager.NontransactionalRead"));
                          initProperties.put("datanucleus.NontransactionalWrite",
                                  System.getProperty("persistencemanager.NontransactionalWrite"));
                          initProperties.put("datanucleus.singletonEMFForName",
                                  System.getProperty("persistencemanager.singletonEMFForName"));
                          initProperties.put("javax.persistence.query.timeout", System.getProperty("persistencemanager.query.timeout"));
                          initProperties.put("datanucleus.datastoreWriteTimeout",
                                  System.getProperty("persistencemanager.datastoreWriteTimeout"));
      
      
                          // Initialize persistence engine.
                          PersistenceManager.initialize(initProperties);
                      }
      
                      @Override
                      public void contextDestroyed(ServletContextEvent servletContextDestroyedEvt) {
                          PersistenceManager.shutdown();
                      }
                  }
      

      All the persistence init properties are defined in app-engine.xml. Its basic structure:

                  <appengine-web-app ...>
                      <application>cloud-project-id</application>
                      <version>1</version>
                      <threadsafe>true</threadsafe>
                      <system-properties>
                          <!-- Cloud platform properties (their name starts with "cloud") -->
                          <property name="cloud.db.url"
                              value="jdbc:google:mysql://(cloud-connection-name)/(db-name)" />
                          <property name="cloud.db.driver"
                              value="com.google.appengine.api.rdbms.AppEngineDriver" />
                              <!--  ...  -->
                          <!-- Dev platform properties (their name starts with "dev") -->
                          <property name="dev.db.url" value="jdbc:mysql://(db-server):(db-port)/(db-name)" />
                          <property name="dev.db.driver" value="com.mysql.jdbc.Driver" />
                              <!--  ...  -->
                          <!-- Datanucleus properties --> 
                          <!-- *********************************************** -->
                          <!-- THESE 2 ARE A MUST-HAVE!!! Others are optional -->
                          <!-- *********************************************** -->
                          <property name="persistencemanager.storeManagerType" value="rdbms" />
      
                          <!-- This means that all DB identifiers MUST be defined in lowercase. -->
                          <property name="persistencemanager.identifier.case" value="LowerCase" /> 
                          <!-- *********************************************** -->
                              <!--  ...  -->
                      </system-properties>
                      <sessions-enabled>true</sessions-enabled>
                      <async-session-persistence enabled="false" />
                      <static-files>
                          <exclude path="/**.xhtml" />
                      </static-files>
                  </appengine-web-app>
      

      You must define at least one persistence unit (in "persistence.xml"):

                  <?xml version="1.0" encoding="UTF-8" ?>
                  <persistence xmlns="http://java.sun.com/xml/ns/persistence"
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                      xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
                                  http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
                      version="1.0">
      
                      <persistence-unit name="MyPersistenceUnit">
                          <!-- DATANUCLEUS' JPA 2.0 PERSISTENCE PROVIDER CLASS -->
                          <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
      
                          <!-- ENTITY CLASSES -->
                          <class>mypackage.dataaccess.entity.Visit</class>
      
                          <!-- DON'T PROCESS UNLISTED CLASSES AS ENTITY CLASSES. -->
                          <exclude-unlisted-classes>true</exclude-unlisted-classes>
                      </persistence-unit>
                  </persistence>
      

      and some initialize and shutdown methods in your persistence manager object(s) to create and destroy the EntityManagerFactory and the EntityManager(s). Something like this:

                      public static void initialize(Map properties) {
                          if (!isInitialized) {
                              if (properties == null) {
                                  emfInstance = Persistence.createEntityManagerFactory("MyPersistenceUnit");
                              } else {
                                  emfInstance = Persistence.createEntityManagerFactory("MyPersistenceUnit", properties);
                              }
                              emInstance = emfInstance.createEntityManager();
                              isInitialized = true;
                          }
                      }
      
                      public static void shutdown() {
                          try {
                              emInstance.close();
                          } catch (Exception e) {}
                          try {
                              emfInstance.close();
                          } catch (Exception e) {}
                      }
      

      The "Visit" class is just an Entity class which maps the 3 fields (Number of visit, source IP and timestamp) and it's registered in the "persistence.xml" file.

      I wrote this post as a tutorial that shows, step-by-step, how I managed to run these technologies on GAE (SDK 1.9.48 by the time I typed these lines in). It's taken me weeks of researching and trial-error coding, and I expect with this guide to help other Java programmers not to go through that mess as I did.

      Hope this guide can help others to create great J2EE apps in GAE.

      这篇关于如何设置带有JSF 2.2,JPA 2.0和依赖注入功能的Google AppEngine Web应用程序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆