Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
Re: [eclipselink-users] Spikes, FOSS JPA 1.0 test framework

Dear Shaun,

I was using EclipseLink M6, I just updated locally to M7 but still have the
problem. FYI Toplink Essentials is V2 B41.
I'll give you some code here, but you can get the whole thing from:

svn checkout https://lab.jugtorino.it/svn/sandbox/spikes/trunk spikes

The framework is using Spring 2.5.3 for the unit tests.

The base domain class (which contains the @PreUpdate)  file is
org/syger/example/domain/AbstractModel.java:

<pre>
package org.syger.example.domain;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;

/**
 * Modello astratto: contiene proprieta' commune a tutte le entita' -
 * Abstract model: contains properties common to all entities.
 * 
 * @author john.leach
 */
@MappedSuperclass
public abstract class AbstractModel {

	@Id 
	@GeneratedValue(strategy = GenerationType.AUTO)
	Long id;
	
	@Version
	@Column(nullable = false)
	Long version;
	
	@Column(updatable = false)
	@Temporal(TemporalType.TIMESTAMP)
	java.util.Date createdAt;
	
	@Temporal(TemporalType.TIMESTAMP)
	java.util.Date lastUpdated;

	public Long getId() { return id; }
	public Long getVersion() { return version; }
	public java.util.Date getCreatedAt() { return createdAt; }
	public java.util.Date getLastUpdated() { return lastUpdated; }

	public AbstractModel() {
		lastUpdated = createdAt = new java.util.Date();
	}

	/**
	 * Settaggio della data del'ultimo aggiornamento prima del salvataggio -
	 * Sets the last updated date prior to saving.
	 */
	@PreUpdate 
	protected void preUpdate() {
		lastUpdated = new java.util.Date();
	}
}
</pre>

The concrete User domain model (which has the not-null fields, and
hashCode() method that causes the NPE) file is
org/syger/example/domain/User.java:

<pre>
package org.syger.example.domain;

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

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

/**
 * User: un utente ha un nome (univoco) ed zero o piu' Client(i) -
 * User: a user has a unique name and zero or more Client(s).
 * 
 * @author john.leach
 */
@Entity 
@Table(name="app_user", // user can be a reserved SQL word
       uniqueConstraints=@UniqueConstraint(columnNames={"name"}))
public class User extends AbstractModel {

	@Basic(optional = false)
	@Column(nullable = false, updatable = false, length = 100)
	String name;

	@Basic(optional = false)
	String info;
	
	@OneToMany(mappedBy = "user", cascade = { CascadeType.ALL }) 
	@OrderBy("name ASC")
	List<Client> clients = new ArrayList<Client>();

	public User() {	
		super();
		info = "";
	}

	public User(String name) {
		this();
		this.name = name;
	}
	
	public String getName() { return name; }
	public void setName(String name) { // needed for Yaml
		if (this.name == null) {
			this.name = name;
		}
	}

	public List<Client> getClients() { return clients; }
	
	public String getInfo() { return info; }
	public void setInfo(String info) { this.info = info; }

	public void addClient(Client newClient) {
		newClient.setUser(this);
		if (clients.contains(newClient)) {
			return;
		}
		clients.add(newClient);
	}
	
	public void removeClient(Client oldClient) {
		if (clients.remove(oldClient)) {
			// oldClient.setUser(null); // removed to keep EclipseLink happy
		}
	}

	public boolean equals(Object other) {
		if (this == other) return true;
        	if (other == null) return false;
        	if ( !(other instanceof User) ) return false;
        	final User that = (User)other;
        	return this.name.equals(that.getName());
	}
	
	public int hashCode() {
		return name.hashCode();
	}
	
	public String toString() {
		return name;
	}
}
</pre>

The test classes use an abstract class, which itself sits on the Spring
framework, file is org/syger/unittest/AbstractJpaExperimentalTests.java:

<pre>
package org.syger.unittest;

import org.springframework.test.jpa.AbstractJpaTests;

import org.syger.example.manager.DataManager;

/**
 * Classe d'aiuto per tutte le unit tests -
 * Helper class for all unit tests.
 * 
 * @author john.leach
 */
public class AbstractJpaExperimentalTests extends AbstractJpaTests {

	private DataManager dataManager;
	
	protected String[] getConfigLocations() {
		return new String[] { "classpath:META-INF/test-spring-config.xml" };
	}

	public DataManager getDataManager() { return this.dataManager; }
	public void setDataManager(DataManager dataManager) { this.dataManager =
dataManager; }
	
	public void log(String message) {
		System.out.println(message);
		System.err.println(message);
	}
}
</pre>

Next is the null test which causes a wrapped org.springframework exception
in EclipseLink, but a java.lang.NPE in TopLink Essentials, plus the
@PreUpdate test, file org/syger/unittest/UserTest.java:

<pre>
package org.syger.unittest;

import org.syger.example.domain.User;

/**
 * Test basiliare per il domain model User -
 * Basic tests for the User domain model.
 * 
 * @author john.leach
 */
public class UserTest extends AbstractJpaExperimentalTests {

	public void testUserNullName() {
		User user = new User();
		try {
			getDataManager().persist(user);
			getDataManager().flush();
			fail("Not null exception expected");
		}
		catch (Exception ex) {
			if (!ex.getClass().getName().startsWith("org.springframework")) {
				ex.printStackTrace(System.out);
				fail("Not the expected exception " + ex.getClass().getName());	
			}				
		}
	}

	// ... other tests removed
	
	public void testUserPreUpdate() {
		User user = new User("updated");
		getDataManager().persist(user);
		getDataManager().flush();

		java.util.Date preUpdated = user.getLastUpdated();
		System.out.println("Original " + preUpdated.getTime());

		long time = System.currentTimeMillis();
		while (System.currentTimeMillis() - time < 1000L) {
			// dum de dum... just waiting a second
		}

		user.setInfo("info");
		getDataManager().flush();

		java.util.Date postUpdated = user.getLastUpdated();
		System.out.println("Changed " + postUpdated.getTime());

		assertFalse("Date didn't change", preUpdated.getTime() ==
postUpdated.getTime());
	}
}
</pre>

You probably want to see the persistence.xml file too (there is no orm.xml
file, or rather it is empty):

<pre>
<?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="JPA-Experiments"
transaction-type="RESOURCE_LOCAL">
        <description>The persistent unit for Example domain
classes.</description>
        
        <class>org.syger.example.domain.AbstractModel</class>
        <class>org.syger.example.domain.User</class>
        <class>org.syger.example.domain.Client</class>
        <class>org.syger.example.domain.Tagging</class>
        <class>org.syger.example.domain.Tag</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        
        <properties>
            <!-- Hibernate properties -->
            <property name="hibernate.cfg.customizer"
value="org.syger.example.domain.HibernateConfiguration"/>
            
            <!-- TopLink Essentials properties -->
            <property name="persistence.tools.weaving" value="true"/>
            <property name="toplink.session.customizer"
value="org.syger.example.domain.TopLinkConfiguration"/>

            <!-- EclipseLink properties -->
            <property name="persistence.tools.weaving" value="true"/>
            <property name="eclipselink.session.customizer"
value="org.syger.example.domain.EclipseLinkConfiguration"/>

            <!-- OpenJPA properties -->
            <property name="openjpa.Log" value="DefaultLevel=TRACE,
Enhance=TRACE, MetaData=TRACE, Runtime=TRACE, Tool=TRACE, SQL=TRACE"/>
        </properties>
    </persistence-unit>
</persistence>
</pre>

Finally, just to make this post a little longer, first the TopLink
Essentials Spring config file:

<pre>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans";
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
       xmlns:p="http://www.springframework.org/schema/p";
       xmlns:aop="http://www.springframework.org/schema/aop";
       xmlns:context="http://www.springframework.org/schema/context";
       xmlns:jee="http://www.springframework.org/schema/jee";
       xmlns:tx="http://www.springframework.org/schema/tx";
       xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
                           http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                           http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
                           http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
                           http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd";>

    <context:annotation-config/>

    <bean id="entityManagerFactory"
         
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="persistenceXmlLocation"
value="classpath:META-INF/persistence.xml"/>
        <property name="persistenceUnitName" value="JPA-Experiments"/>
        <property name="jpaVendorAdapter">
            <bean
class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
                <property name="databasePlatform"
value="oracle.toplink.essentials.platform.database.HSQLPlatform"/>
                <property name="showSql" value="true"/>
                <property name="generateDdl" value="true"/>
            </bean>
        </property>
    </bean>

    <bean id="dataSource" 
         
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
          destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:mem:testDb"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>
    
    <bean id="transactionManager"
          class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean
class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
    <bean
class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

    <bean id="tagManager" class="org.syger.example.manager.TagManager"
scope="prototype"/>
    <bean id="dataManager" class="org.syger.example.manager.DataManager"
scope="prototype">
        <property name="tagManager" ref="tagManager"/>
    </bean>
</beans>
</pre>

Then the EclipseLink Spring config file:

<pre>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans";
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
       xmlns:p="http://www.springframework.org/schema/p";
       xmlns:aop="http://www.springframework.org/schema/aop";
       xmlns:context="http://www.springframework.org/schema/context";
       xmlns:jee="http://www.springframework.org/schema/jee";
       xmlns:tx="http://www.springframework.org/schema/tx";
       xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
                           http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                           http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
                           http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
                           http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd";>

    <context:annotation-config/>

    <bean id="entityManagerFactory"
         
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="persistenceXmlLocation"
value="classpath:META-INF/persistence.xml"/>
        <property name="persistenceUnitName" value="JPA-Experiments"/>
        <property name="jpaVendorAdapter">
            <bean
class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
                <property name="databasePlatform"
value="org.eclipse.persistence.platform.database.HSQLPlatform"/>
                <property name="showSql" value="true"/>
                <property name="generateDdl" value="true"/>
            </bean>
        </property>
    </bean>

    <bean id="dataSource" 
         
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
          destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:mem:testDb"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>
    
    <bean id="transactionManager"
          class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean
class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
    <bean
class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

    <bean id="tagManager" class="org.syger.example.manager.TagManager"
scope="prototype"/>
    <bean id="dataManager" class="org.syger.example.manager.DataManager"
scope="prototype">
        <property name="tagManager" ref="tagManager"/>
    </bean>
</beans>
</pre>

Again, you might find it quicker just to grab the Subversion trunk. I run
all the tests via an ant script within a DOS prompt, on Windows XP
Professional SP2. The computer was surrounded by a 3 inch thick anti-murphy
barrier, which I will be sending back to the manufacturers...

Sorry about the <pre></pre> marks, I thought they'd work

Hope that helps,

John


Shaun Smith wrote:
> 
> Hi John,
> 
>> Shaun Smith wrote:
>>   
>>> In terms of the problem you're addressing, things have changed since you 
>>> wrote the article.
>>>     
>>
>> It's only a couple of week old!
>>   
> No one can say we're sitting around doing nothing! ;-)
> ...
>> FYI, I have now updated Spikes to use TopLink Essentials, and there have
>> been a couple of problems:
>>
>> 1. Deliberately setting a non null field to null, and then attempting to
>> persist the object does not give back a Spring wrapped exception (as it
>> does
>> in EclipseLink), but rather the infamous NPE trying to call hashCode -
>> which
>> I assume means that TopLink is trying to put the object in its own cache,
>> without any 'formal' validation checks first - that's my hypothesis, more
>> than likely to be wrong. Here's a piece of the stack dump:
>>   
> Can you post the class you're trying to persist and it's mappings?
> ...
>> 2. Both TopLink and EclipseLink fail to update a @PreUpdate annotated
>> method
>> when that method is not  public - when it's public things work just fine.
>> Yet the specs state that (section 3.5.1)
>>
>> The callback methods can have public, private, protected, or package
>> level
>> access, but must not be
>> static or final.
>>
>> Here's the method:
>>
>> 	/**
>> 	 * Settaggio della data del'ultimo aggiornamento prima del salvataggio -
>> 	 * Sets the last updated date prior to saving.
>> 	 */
>> 	@PreUpdate 
>> 	protected void preUpdate() {
>> 		lastUpdated = new java.util.Date();
>> 	}
>>
>> Change protected to public and Robert is you proverbial father's brother.
>> This is not a showstopper of course, I'm personally not that squeemish
>> about
>> making the method public.
>>   
> I couldn't reproduce this problem with EclipseLink M7.  I began a 
> transaction, queried an object, modified a String field, and committed 
> the transaction.  I tried package, private, protected, and public.  Each 
> time my @PreUpdate method was called.
> 
>     Shaun
> -- 
> 
> 
> Oracle <http://www.oracle.com>
> Shaun Smith | Principal Product Manager, TopLink | +1.905.502.3094
> Oracle Fusion Middleware
> 110 Matheson Boulevard West, Suite 100
> Mississauga, Ontario, Canada L5R 3P4
> 
> _______________________________________________
> eclipselink-users mailing list
> eclipselink-users@xxxxxxxxxxx
> https://dev.eclipse.org/mailman/listinfo/eclipselink-users
> 
> 

-- 
View this message in context: http://www.nabble.com/Spikes%2C-FOSS-JPA-1.0-test-framework-tp17201935p17349805.html
Sent from the EclipseLink - Users mailing list archive at Nabble.com.



Back to the top