Skip to main content

Using Java Web Start with the Java Persistence API

Posted by lancea on June 15, 2007 at 9:02 AM PDT

"http://www.w3.org/TR/html4/loose.dtd">



Untitled Document


I created a simple Java Persistence API application using Toplink Essentials which runs like a champ from NetBeans and from the
command line. So I figure, great, I will spice up the example a little more and use Java Web Start (yes I am a rookie to Java Web Start, so be gentle!) and I whip up a simple jnlp file JPAWebStart.bad.jnlp:




  
    JPAWebStart fail
    Company, Inc.</vendor>
    <homepage href="homepage.html"/>
    </description>
   
   
   
 

 
   
 

 
   
 

 
   
 

 
   

 

 

Ok, off I go to run my application:

javaws JPAWebStart.bad.jnlp

And low and behold, I get the following error:

 

Exception in thread "AWT-EventQueue-0" 
javax.persistence.PersistenceException: No Persistence provider for EntityManager named JPAWebStartPU
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:89)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:60)
at demo.ContactImpl$1.run(ContactImpl.java:37)
at java.security.AccessController.doPrivileged(Native Method)
at demo.ContactImpl.(ContactImpl.java:34)
at demo.ContactFactory.getInstance(ContactFactory.java:28)
at demo.ContactTableModel.(ContactTableModel.java:34)
at demo.ui.JPAWebStartDemo.initComponents(JPAWebStartDemo.java:134)
at demo.ui.JPAWebStartDemo.(JPAWebStartDemo.java:23)
at demo.ui.JPAWebStartDemo$7.run(JPAWebStartDemo.java:304)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)

Why is my Persistence provider not found when I can run the application fine as a standalone application from the command line or from NetBeans?

The error message is misleading as the issue turns out to be due to the sandbox environment that Java Web Start uses to protect users from untrusted applications.

So where do we go from here? Well, we will create two jnlp files, one to start our application, the other which contains the classes which require more access then the sandbox allows. The jar files contained in the second jnlp, which is referenced from our first jnlp file, will enable access to the system and all jar files listed as resources will be signed.

Here is the new JNLP file, JPAWebStart.jnlp, used to launch my application:




  
    JPAWebStart Demo
    Company, Inc.</vendor>
    <homepage href="homepage.html"/>
    </description>
   
   
   
 

 
   
 

   
   
 

 
   
 

 

I made the following changes to JPAWebStart.jnlp:

  • Start the application via a new program which resides in the jar JPAWebStartMain.jar

  • Use the extension element to reference the JPAPermissions.jnlp which contains the jars that needed to be signed as well as grant the access beyond what is allowed by the sandbox

Here is the JPAPermissions.jnlp which contains all of the signed jars and enables the extra security access:




  
    JPAPermissions
    Company, Inc.</vendor>
    <homepage href="homepage.html"/>
    </description>
   
   
   
 

  
     
  

 
   
 

 
   
 

 
   
 

 

Next, we need to sign the jars. To do this I added a -post-jar target to the default build.xml in my NetBeans project:

    
       
       
       
       
       
       
       
       
       
       
                     basedir="build/classes"
             includes="demo/main/**"
             manifest="MANIFEST_MAIN.MF"
         \>
   

One other addition to the -post-jar target is the creation of the jar file that is referenced in JPAWebStart.jnlp. Why am I doing it here you ask? Well, it was easier then overriding all of the default targets for building the application that NetBeans provides (if someone has an alternative suggestion, let me know).

About the JPAWebStart sample application

The sample application demonstrates the use of the Java Persistence API from Java SE, using Swing, Java DB and Java Web Start:

When the application is started the main screen is shown:

There are no contacts in the database when the application is started. To add contacts to the database, you would select the Contacts menu item, "Add Contact"

and the following form will be opened:

The main entry point to the application is the JPADemo class. JPADemo is packaged in the jar JPAWebStartMain.jar and its sole purpose is to start the rest of the application which requires more permissions than the sandbox allows:

package demo.main;
import demo.ui.JPAWebStartDemo;
public class JPADemo {
   
    /** Creates a new instance of JPADemo */
    public JPADemo() {
    }
   
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
       java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new JPAWebStartDemo().setVisible(true);
            }
        });
    }
   
}

As this is a simple demo, there is only one Entity used by the application, Contact:

package demo.entity;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

/**
* Entity class Contact
*
*/

@Entity
@Table(name="Contacts")
public class Contact implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;
    private String lastName;
    private String phone;
   
    /**
     * Creates a new instance of Contact
     */
    public Contact() {
    }

    /**
     * Creates a new instance of Contact
     */
    public Contact(String firstName, String lastName, String phone) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.phone = phone;
    }
    /**
     * Gets the id of this Contact.
     *
     * @return the id
     */
    public Long getId() {
        return this.id;
    }

    /**
     * Sets the id of this Contact to the specified value.
     *
     * @param id the new id
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * Returns a hash code value for the object.  This implementation computes
     * a hash code value based on the id fields in this object.
     * @return a hash code value for this object.
     */
    @Override
    public int hashCode() {
        int hash = 0;
        hash += (this.id != null ? this.id.hashCode() : 0);
        return hash;
    }

    /**
     * Determines whether another object is equal to this TestPerson.  The result is
     * [prettify]true
if and only if the argument is not null and is a TestPerson object that * has the same id field values as this object. * * @param object the reference object with which to compare * @return true if this object is the same as the argument; * false otherwise. */ @Override public boolean equals(Object object) { // TODO: Warning - this method won't work in the case the id fields are not set if (!(object instanceof Contact)) { return false; } Contact other = (Contact)object; if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) return false; return true; } /** * Returns a string representation of the object. This implementation constructs * that representation based on the id fields. * @return a string representation of the object. */ @Override public String toString() { return "entity.Contact[id=" + id + "], firstName= " + firstName + ", lastName= " + lastName + ", phone= " + phone; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } } [/prettify]

To persist a new Contact, the addContact() method of ContactImpl is called:

public void addContact(Contact contact) {
        try{
            em = emf.createEntityManager();
            em.getTransaction().begin();
            em.persist(contact);
            em.getTransaction().commit();
            System.out.println("row created");
           
        } finally {
            em.close();
        }
    }

Because this application is strictly a Java SE application and not running inside of a container, the application must do the following:

  • Explicitly create an EntityManagerFactory by calling Persistence.createEntityManagerFactory()
  •   emf = Persistence.createEntityManagerFactory("JPAWebStartPU"); 

     

  • Create an application managed EntityManager by calling the method createEntityManager() on an EntityManagerFactory instance in each method as an EntityManager is lightweight and not thread safe
  • Explicitly control the transaction (this is a Resource-Local Transaction). The application must acquire an instance of EntityTransaction and then call the methods begin(), commit() and rollback() as needed.
  • Close the EntityManger by calling the close() method. It is recommended that this is done in a finally block to guarantee that the EntityManager is closed.

One other thing to note is that in order to run TopLink Essentials with Java Web Start, you must enable privileges for the call to Peristence.createEntityManagerFactory which is done in the ContactImpl constructor:

    public ContactImpl() {
           
            emf = (EntityManagerFactory ) AccessController.doPrivileged(
                    new PrivilegedAction() {
                public Object run() {
                    return Persistence.createEntityManagerFactory("JPAWebStartPU");
                }
            }
            );

        }

 

If you do not enable privileges for Peristence.createEntityManagerFactory, then you will encounter the following Exception:

----------------------------------------------------
Exception in thread "AWT-EventQueue-0" java.security.AccessControlException: access denied (java.util.PropertyPermission toplink.logging.level read)
at java.security.AccessControlContext.checkPermission(Unknown Source)
at java.security.AccessController.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkPropertyAccess(Unknown Source)
at java.lang.System.getProperty(Unknown Source)
at oracle.toplink.essentials.internal.ejb.cmp3.JavaSECMPInitializer.getTopLinkLoggingLevel(JavaSECMPInitializer.java:99)
at oracle.toplink.essentials.internal.ejb.cmp3.JavaSECMPInitializer.initializeFromMain(JavaSECMPInitializer.java:272)
at oracle.toplink.essentials.internal.ejb.cmp3.JavaSECMPInitializer.getJavaSECMPInitializer(JavaSECMPInitializer.java:80)
at oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider.createEntityManagerFactory(EntityManagerFactoryProvider.java:118)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:83)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:60)
at demo.ContactImpl.(ContactImpl.java:42)
at demo.ContactFactory.getInstance(ContactFactory.java:28)
at demo.ContactTableModel.(ContactTableModel.java:34)
at demo.ui.JPAWebStartDemo.initComponents(JPAWebStartDemo.java:134)
at demo.ui.JPAWebStartDemo.(JPAWebStartDemo.java:23)
at demo.main.JPADemo$1.run(JPADemo.java:30)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source

When we configure the persistence.xml we will need to indicate that we are using a Resource-Local Transaction



" title="http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
">http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  transaction-type="RESOURCE_LOCAL">
    oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider
    demo.entity.Contact
   
     
     
     
     
     
     
   

 


 

After adding a few contacts, you can then query the database allowing for the rows to either be deleted or modified:

The application provides an implementation of the JTable TableModel interface, ContactTableModel, which is used to manage the display and modification of the rows within the JTable.

    
package demo;

import demo.entity.Contact;
import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
/*
* ContactTableModel.java
*
* Created on May 23, 2007, 4:59 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/

/**
*
* @author Owner
*/
public class ContactTableModel extends AbstractTableModel {
   
    private  List columnNames;
    private List contacts;
    private Contacts contactImpl;
   
    private final int FIRSTNAME = 0;
    private final int LASTNAME = 1;
    private final int PHONE = 2;
   
    /**
     * Creates a new instance of ContactTableModel
     */
    public ContactTableModel() {
       
        contacts = new ArrayList();
        contactImpl = ContactFactory.getInstance(ContactFactory.ConfigApi.USE_JPA);
        columnNames = new ArrayList();
        columnNames.add( "FIRST NAME");
        columnNames.add( "LAST NAME");
        columnNames.add( "PHONE NUMBER");
       
    }
   
    /*
     * Return the Column name for the current column
     */
    public String getColumnName(int column) {
        return columnNames.get(column);
       
    }
   
    /*
     * Return the number of Rows in our table
     */
    public int getRowCount() {
        return contacts.size();
    }
   
    /**
     * Return the number of Columns in our table
     */
    public int getColumnCount() {
        return columnNames.size();
    }
   
    /**
     * Allow all columns to be updatable
     * @param  row Row number in the JTable
     * @param  col Column number for the row in the JTable
     */
    public boolean isCellEditable(int row, int col) {
        // All columns are edititable
        return true;
       
    }
   
    /**
     * Return the column value for the specified  columnIndex and rowIndex.
     * @param  rowIndex Row number in the JTable
     * @param  columnIndex Column number for the row in the JTable
     */
    public Object getValueAt(int rowIndex, int columnIndex) {
        String result = null;
        if (contacts.size() != 0) {
            Contact c = contacts.get(rowIndex);
           
            switch( columnIndex) {
                case FIRSTNAME:
                    result = c.getFirstName();
                    break;
                case LASTNAME:
                    result = c.getLastName();
                    break;
                case PHONE:
                    result = c.getPhone();
                    break;
            }
           
        }
        return result;
    }
   
    /**
     * Sets the column value specified at columnIndex and rowIndex to value.
     * @param  value New value for the column
     * @param  rowIndex Row number in the JTable
     * @param  columnIndex Column number for the row in the JTable
     */
    public void setValueAt(Object value, int rowIndex, int columnIndex) {
       
        System.out.println("Setting value at " + rowIndex + "," + columnIndex
                + " to " + value + " (an instance of " + value.getClass() + ")");
       
        Contact c = contacts.get(rowIndex);
        String result = null;
        switch( columnIndex) {
            case FIRSTNAME:
                c.setFirstName((String) value);
                break;
            case LASTNAME:
                c.setLastName((String) value);
                break;
            case PHONE:
                c.setPhone((String) value);
                break;
        }
       
        contacts.set(rowIndex, c);
        fireTableCellUpdated(rowIndex, columnIndex);
    }
   
    /**
     * Add a new contact
     * @param p  A new Contact(entity)
     *
     */
    public void addContact(Contact p) {
        if (p != null) {
            contactImpl.addContact(p);
        } else {
            System.out.println("No row to persist");
        }
    }
    /**
     * Query the database for Contacts using the criteria specified
     * @param firstName  First name of the contact
     * @param lastName   Last name of the contact
     * @param phone      Phone number of the contact
     * @return           The number of rows that matched the selection criteria
     */
    public int executeQuery(String firstName, String lastName, String phone) {
        System.out.println("first=" + firstName + "- last ="+ lastName + "- phone=" + phone +"-");
        contacts= contactImpl.executeQuery( firstName,  lastName,  phone);
       
        if (contacts.size() != 0 ) {
            fireTableDataChanged();
        }
        return contacts.size();
    }
    /**
     * Utility method which is just used for debugging
     */
    public Contact getRow(int row ) {
        return contacts.get(row);
    }
   
    /**
     * Delete the selected row from the JTable and database
     * @param selectedRow  The row number displayed in the JTable to delete from the database
     */
    public void deleteContact(int selectedRow) {
        contactImpl.removeContact(contacts.get(selectedRow));
        contacts.remove(selectedRow);
        fireTableRowsDeleted(selectedRow, selectedRow);
       
    }
    /**
     * Update the selected row in the JTable and database
     * @param selectedRow  The row number displayed in the JTable to update in the database
     */
    public void updateContact(int selectedRow) {
        contactImpl.updateContact(contacts.get(selectedRow));
    }
   
}

The ContactTableModel invokes the static factory ContactFactory.getInstance() to obtain an instance of ContactImpl which in our example is implemented using the Java Persistence API. This factory allows for providing additional implementations for accessing the database such as JDBC.

 
contactImpl = ContactFactory.getInstance(ContactFactory.ConfigApi.USE_JPA);

When a query is submitted, it is built as a dynamic query. Normally you will find the use of Named queries more efficient. However, sometimes you will find you need to use a dynamic query based
on the number of parameters that might be optional.

 
    public List executeQuery(String firstName, String lastName, String phone) {
        Query q = null;
        List result = null;
        boolean paramSpecified = false;
        StringBuffer buf = new StringBuffer("Select c from Contact c where ");
        String query = null;       
       
        if (!firstName.equals(EMPTYSTRING)) {
            paramSpecified = true;
            buf.append("upper(c.firstName) like :firstName and ");
        }
        if (!lastName.equals(EMPTYSTRING)) {
            paramSpecified = true;
            buf.append("upper(c.lastName) like :lastName and ");
        }
        if (!phone.equals(EMPTYSTRING)) {
            paramSpecified = true;
            buf.append("upper(c.phone) like :phone and ");
        }
       
        // If there are parameters specified, then remove the last "and" otherwise
        // remove "where" from the end of the query.
        if (paramSpecified) {
            query = buf.substring(0, buf.length() - " and".length());
        } else {
            query = buf.substring(0, buf.length() - " where ".length());
        }
        System.out.println("Query is "" + query + """);
       
        try {
            em = emf.createEntityManager();
            q = em.createQuery(query);
           
            if (!firstName.equals(EMPTYSTRING)) {
                q = q.setParameter("firstName", "%"+firstName.toUpperCase()+"%");
            }
            if (!lastName.equals(EMPTYSTRING)) {
                q = q.setParameter("lastName", "%"+lastName.toUpperCase()+"%");
            }
            if (!phone.equals(EMPTYSTRING)) {
                q = q.setParameter("phone", "%"+ phone.toUpperCase()+"%");
            }
           
            result = q.getResultList();
            System.out.println("rows returned=" + result.size());
        } finally {
            em.close();
        }       
        return result;
    }     

All of the rows that are displayed in the JTable are detached and must be managed by the persistence context prior to committing the transaction. This is accomplished by calling the EntityManager method merge() prior to committing the transaction:

    
    /**
     * Remove a contact to the database.  The contact that is
     * passed in, needs to be merged prior to removing.
     * @param  contact  The Contact to remove
     */
    public void removeContact(Contact contact) {
        try {
            if(contact  != null) {
                em = emf.createEntityManager();
                em.getTransaction().begin();
                Contact mergedContact = em.merge(contact);
                em.remove(mergedContact);
                em.getTransaction().commit();
                System.out.println("row removed");
            }
        } finally {
            em.close();
        }      
       
    }

     /**
     * Update a contact in the database.  The contact that is
     * passed in, needs to be merged prior to the update.
     * @param  contact  The Contact to update
     */
    public void updateContact(Contact contact) {
        try {
            em = emf.createEntityManager();
            em.getTransaction().begin();
            em.merge(contact);
            em.getTransaction().commit();
            System.out.println("row :" + contact + " updated");
           
        } finally {
            em.close();
        }       
    }   

Configuring the JPAWebStart application

The following steps are needed to configure the JPAWebStart application:

 

  • If you are going to run the JPAWebStart application using the javaws command, then you need to modify the jnlp elements in the files JPAWebStart.jnlp and JPAPermissions.jnlp files specifying the correct codebase:
       
    "file:///C:/Netbeans/JPAWebStart" href="http://weblogs.java.net/blog/lancea/archive/JPAWebStart.jnlp">
     

    For, example if you are on unix and installed JPAWebStart in /tmp, you would modify the jnlp element in JPAWebStart.jnlp and JPAPermissions.jnlp to be:

       
    "file:///tmp/JPAWebStart" href="JPAWebStart.jnlp">
     

Running the JPAWebStart application

You can run the JPAWebStart application using Java Web Start in multiple ways:

  • Using the javaws command
       
    javaws JPAWebStart.jnlp
  • Running using the NetBeans Java Web Start plugin:

 

  • Deploying and then running from a web browser using the NetBeans Java Web Start plugin:

 

      • After the application deploys, a web browser will be launched allowing you to start the JPAWebStart application:

 

Summary

This blog provides an overview of how to successfully develop a Java SE application using Java Web Start, Java Persistence API, Swing and NetBeans 5.5. In order to use Java Web Start with the Java Persistence API, you must enable security and sign the jars for the classes which need more access than the Java Web Start sandbox allows.

The following are additional references which you might find useful when developing applications using the technologies demonstrated in this article:

 

Special thanks to Mitesh, Markus and Ryan for a quick review, testing and suggestions for the blog..

 

     

 


Related Topics >>

Comments

Security Exception

When I tried to run this example via webstart from webserver it throws the security exception:
Exception in thread "AWT-EventQueue-0" java.lang.SecurityException: untrusted class package "demo.main" in class path at com.sun.deploy.security.CPCallbackHandler$ParentElement.checkResource(Unknown Source) at com.sun.deploy.security.DeployURLClassPath$JarLoader.checkResource(Unknown Source) at com.sun.deploy.security.DeployURLClassPath$JarLoader.getResource(Unknown Source) at com.sun.deploy.security.DeployURLClassPath.getResource(Unknown Source) at java.net.URLClassLoader$1.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(Unknown Source) at com.sun.jnlp.JNLPClassLoader.findClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at demo.main.JPADemo$1.run(JPADemo.java:30) at java.awt.event.InvocationEvent.dispatch(Unknown Source) at java.awt.EventQueue.dispatchEvent(Unknown Source) at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.run(Unknown Source)
This exception is described here http://download-llnw.oracle.com/javase/6/docs/technotes/guides/jweb/mixe... but I don't understand how to fix this problem. Could you help me with this please? Thanks

Where Is The DB Located?

Nice article!!!

This is exactly what I am looking for, thank you so much for putting this up. I have couple questions though:

  • 1. Where is the DB located on client's machine?
  • 2. If I push a new version of JWS and it pushes to client machine, does it overwrite the existing DB?


Thanks in advance!!

Got The Answer

Hi,

Please discard my previous questions, I think I got the answer by doing little research. For anyone who is interested in knowing this, when connecting to database, you can set file path in connection string:

String connectionURL = "jdbc:derby:c:\\temp\\" + dbName + ";create=true";

This will put your database into particular file path. And even your JWS app is being pushed to newer version, it doesn't overwrite the database and all you have to do is to write bootstrap code to check if DB exists or not.

Thanks.

Hi, we have one

Hi,
we have one application[Desktop Swing application] which runs on Java Web start , For DB connection it uses JPA as ORM tool. Here I found some serious Issues in the performance point of view.At the start of application it loads EntityManagerFactory
EntityManagerFactory emf = Persistence.getEntityManagerFactory("manager1");
which took almost 26 sec. But the same application when I run using java command it took just 2 sec to load EntityManager.
Does anybody know what would be the reason behind this, I tried lot but I didn't find perfect solution for this Issue.
Thanx.