Trials with JPA and XPages

Intro

Don’t get me wrong, I love working with Domino and documents and all that 😉 , but with XPages I sometimes want to use good old fashioned SQL or even hybrid solutions. I do not want to be bothered with writing sql statements myself in the DAO classes, and I just want to use a prebuilt solution that just works. Enter JPA.

Before I go too much into this, I want to thank the guys on Slack for giving me a hand with the bits and pieces of this. I had a great deal of problems getting this thing going and it is still very far from perfect. I can at least say, however, that it runs.

Quick trial history

I started out with a simple PoC concept of trying to build a type of registry for DVDs I have. (Not creative, just quick and simple). I also decided to do this with eclipselink and built in Eclipse mars. I quickly built a few entity classes and I used the xml orm to map those entities to the database. When this was done, I exported that project to a jar file and then imported that jar into an *.nsf. This first try failed. I am sure I can think of numerous reasons why it did not work. The main issue I had was that the persistence unit I tried to configure could not be found on the runtime. At this point, I copied the eclipselink jars into the nsf directly, and copied the entities into the .nsf.  In other words, the JPA Layer was no longer its own single jar. This allowed me to try to move the configuration files around to other locations. I tried everything I could think of. Fail.

Rethink and Rework

(in other words, ask around…)

Let me just say that if you are not already a member of the XPages Slack community, why aren’t you? JOIN!!! xpages.slack.com

I went into the random channel, and posted a general question if anyone had success with JPA and XPages. Jesse Gallagher pointed out this post by Toby Samples which uses the hibernate JPA framework. I had seen this presentation before, but I must admit that it lacks the meat and potatoes needed to get it off the ground.  Dont get me wrong, it is a great resource! The other reason why I did not do much with this at first is that it was done in eclipse and not Notes Designer. Most of the stuff that I program for XPages is done directly in Designer and not in eclipse. After talking to the guys on Slack, and seeing that Toby Samples had success with hibernate, I decided to indeed give it a try.

Downloading Hibernate

In the slides (see the above link), Toby describes talks about hibernate tools being downloaded and installed in eclipse. As we all know, Notes is now based off of eclipse, albeit an older version of eclipse…  After a lot of searching, I did find an updateSite with the tools and I downloaded a copy of them. I then installed them onto my client as a Widget and also onto the server. This really did nothing worth while.  I could not access the tools in designer, nor were they available on the server. I deleted them pretty quickly. Instead, I found this site to download an older version of the ORM. It is necessary to take the 4.3.11 version because Notes/Domino runs on a bitterly out-dated version of java. Once this is downloaded, I imported the required jars into my .nsf. I also put these jars into the <domino>/jvm/lib/etc/ directory as described in the slides. The only issues I had at this point was that designer couldn’t process the source quickly enough to give me the code suggestions and I had the feeling that designer was always a step away from crashing.  Indeed it did crash once or twice… (After a system restart it seems to have gotten better)

Configuration and Setup

The first thing that I did was to create the hibernate.cfg.xml file. This is all pretty straight forward.  I am also not going to discuss how this file is to be created, but I will show you my copy…

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC 
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
   <session-factory name="hibernateSessionFactory">
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
            <property name="hibernate.connection.password">****</property>
            <property name="hibernate.connection.url">jdbc:mysql://192.168.0.1:3306/library?createDatabaseIfNotExist=false</property>
            <property name="hibernate.connection.username">DB_Programmatic_User</property>
            <property name="hibernate.show_sql">true</property>
            <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
            <property name="hibernate.search.autoregister_listeners">false</property>
            <!-- <property name="hibernate.hbm2ddl.auto">create</property>  -->

            <mapping class="de.domain..bluemix.jpa.entities.Actor"></mapping>
            <mapping class="de.domain.bluemix.jpa.entities.DVD"></mapping>
            <mapping class="de.domain.jpa.entities.Genre"></mapping>	
      </session-factory>
      
      
</hibernate-configuration>

The second thing I did was create an application listener with static information for creating sessions.

package de.domain.mysqltrial.services;

import java.io.Serializable;

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

import com.ibm.xsp.application.ApplicationEx;
import com.ibm.xsp.application.events.ApplicationListener2;

public class PersistenceManager implements Serializable, ApplicationListener2 {

      private static final long serialVersionUID = 1L;

      private static SessionFactory sessionFactory;

      private static boolean init = false;
      
      public static SessionFactory getSessionFactory() {
            if((sessionFactory == null) || (sessionFactory.isClosed())){
                  throw new IllegalStateException("Session Factory is null or closed!");
            }
            return sessionFactory;
      }
      
      public static boolean isInit() {
            return init;
      }
      
      private void init(){
            if(!isInit()){
                  try{
                        System.out.println("Initializing Session Factory");
                        
                        Configuration conf = new Configuration().configure();
                        ServiceRegistry serviceRegistry= new StandardServiceRegistryBuilder().applySettings(conf.getProperties()).build();
                        sessionFactory = conf.buildSessionFactory(serviceRegistry);
                        init = true;
                  } catch(Throwable t){
                        t.printStackTrace();
                  }
            }
      }

      public void reInit(){
            destroy();
            init();
      }
      
      private void destroy(){
            System.out.println("Destroying Entity Manager Factory");
            init = false;
            if(sessionFactory != null)	sessionFactory.close();
            sessionFactory = null;
      }

      public void applicationCreated(ApplicationEx arg0) {
            init();
      }

      public void applicationDestroyed(ApplicationEx arg0) {
            destroy();
      }

      public void applicationRefreshed(ApplicationEx arg0) {
            reInit();
      }
}

The point of the application listener is to make sure clean up is done correctly and to make sure that everything is initialized correctly.  It probably is not needed in this way, but I found it at the very least to be a cool idea. This class must also be registered. Here is a screen shot with the location of these files.

package

 

 

 

 

 

Primary Problem

This configuration worked…. almost. I kept getting security exceptions. The runtime was not being granted the permissions it needed to run the code. Only after I added the following lines to the java.policy document was I able to get the code to execute properly.

permission java.lang.RuntimePermission "getClassLoader"; 
permission java.lang.RuntimePermission "setContextClassLoader"; 

permission java.util.PropertyPermission "jboss.i18n.generate-proxies" "write"
permission java.security.AllPermission;

This is a situation that I find sucky. It is alright for a test environment, but I would not want to mess with the policy file for an end-customer. My question is, does anyone have a possible solution for this?

Conclusion

It is possible to use JPA and it works well,  but I am not happy about the wide-open security window that was necessary in order to get it to work.

One more time, thank you to those on Slack who gave me a hand…

Toby Samples, David Leedy, Jesse Gallagher, and others who had hints…