[hibernate-issues] [Hibernate-JIRA] Created: (HV-246) BeanValidationEventListener has a bug (technically a problem with hibernate-annotations 3.5.0beta1... but I couldn't find that project)

Ken Egervari (JIRA) noreply at atlassian.com
Thu Oct 1 16:42:50 EDT 2009


BeanValidationEventListener has a bug (technically a problem with hibernate-annotations 3.5.0beta1... but I couldn't find that project)
---------------------------------------------------------------------------------------------------------------------------------------

                 Key: HV-246
                 URL: http://opensource.atlassian.com/projects/hibernate/browse/HV-246
             Project: Hibernate Validator
          Issue Type: Bug
    Affects Versions: 4.0.0.CR1
         Environment: hibernate 3.3.2ga, hibernate validator 4.0cr1, hibernate-annotations 3.5.0beta1
            Reporter: Ken Egervari
            Priority: Blocker


The interaction with a hibernate session and sessions created within a validator do not work together.

For example, here is a test case:

{code}
	@Test
	public void jobSeekersCanSaveAPreviousResumeWithTheSameName() {
		JobSeekerResume resume = ( JobSeekerResume ) resumeDao.find( 1 );
		resume.setDescription( "new desc." );

		resumeDao.save( resume );
		flush();

		jdbcMap = simpleJdbcTemplate.queryForMap(
			"select * from resume where resume_id = ?", resume.getId() );

		assertEquals( resume.getDescription(), jdbcMap.get( "DESCRIPTION" ) );
	}
{code}

the flush() method creates an exception:

{code}
ERROR AssertionFailure:45 - an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
org.hibernate.AssertionFailure: collection [jawbs.domain.jobseeker.JobSeeker.categories] was not processed by flush()
	at org.hibernate.engine.CollectionEntry.postFlush(CollectionEntry.java:228)
	at org.hibernate.event.def.AbstractFlushingEventListener.postFlush(AbstractFlushingEventListener.java:356)
	at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
	at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1028)
	at jawbs.DatabaseTests.flush(DatabaseTests.java:113)
{code}

Obviously hibernate core is saying this could be a bug, or it could be unsafe.

Now, the reason this happens is because JobSeekerResume has a contraint that accesses the hibernate session. Now, I have many, many constraints that use this constraint and they work fine. This is the only test that is causing problems for me, and I don't have a clue why.

Nonetheless, I'm going to give you a lot of code. Here is the constraint annotation on JobSeekerResume:

{code}
@QueryConstraint(
	hql = "select count(*) from JobSeekerResume where name = :name and id != :id and jobSeeker.id = :jobSeeker.id",
	message = "{jobSeekerResume.name.unique}",
	enabled = true
)
{code}

And here is the code for the validator implementation:

{code}
public class QueryConstraintValidator extends ValidatorSupport<QueryConstraint,DomainObject> {

	/* Fields */
	private String hql;
	private boolean enabled;

	/* Services */
	public void initialize( QueryConstraint queryConstraint ) {
		this.hql = queryConstraint.hql();
		this.enabled = queryConstraint.enabled();
	}

	public boolean isValid( DomainObject domainObject ) {
		return isValid( domainObject, null );
	}

	@Override
	protected boolean preValidate() {
		return !enabled;
	}

	protected boolean testQuery( Session session, DomainObject domainObject ) {
		logger.debug( "Enabled - Validating constraint with: " );
		logger.debug( hql );

		Query query = session.createQuery(
			HqlParser.removePeriodsFromParameterNames( hql )
		);

		BeanWrapper beanWrapper = new BeanWrapperImpl( domainObject );
		for( String parameterName : HqlParser.getParameterNames( hql ) ) {
			query.setParameter(
				HqlParser.removePeriodsFromParameterName( parameterName ),
				beanWrapper.getPropertyValue( parameterName )
			);
		}

		boolean result = (Long) query.uniqueResult() == 0;

		logger.debug( "isValid is returning: " + result );

		return result;
	}
...
}
{code}

The support constraint (the important one most likely) is as follows:

{code}
public abstract class ValidatorSupport<T extends Annotation,U> implements ConstraintValidator<T,U> {

	/* Fields */
	protected static Logger logger =
		LoggerFactory.getLogger( QueryConstraintValidator.class );

	/* Services */
	public boolean isValid( U object, ConstraintValidatorContext constraintValidatorContext ) {
		if( preValidate() ) {
			return true;
		}

		SessionFactory sessionFactory =
			( SessionFactory ) ApplicationContextProvider.getBean(
				"sessionFactory" );

		if( sessionFactory != null ) {
			Session session = sessionFactory.openSession();
			Transaction tx = session.beginTransaction();

			boolean result = testQuery( session, object );

			tx.commit();
			session.close();

			return result;
		}

		return true;
	}

	protected boolean preValidate() {
		return false;
	}

	protected abstract boolean testQuery( Session session, U object );
}
{code}

Lastly, here is the mapping of the JobSeeker subclass:

{code}
        <subclass name="jawbs.domain.jobseeker.JobSeeker" discriminator-value="ROLE_JOBSEEKER">
            <property name="isPrivate" column="is_private" />
            <bag name="categories" table="job_seeker_to_category" cascade="all-delete-orphan" inverse="true">
                <key column="job_seeker_id"/>
                <many-to-many class="jawbs.domain.Category" column="category_id"/>
            </bag>
            <bag name="watchedJobs" table="job_seeker_to_watched_job" cascade="all-delete-orphan" inverse="true">
                <key column="job_seeker_id"/>
                <many-to-many class="jawbs.domain.job.Job" column="watched_job_id"/>
            </bag>
            <bag name="applications" table="candidate" cascade="all-delete-orphan" inverse="true">
                <key column="job_seeker_id"/>
                <many-to-many class="jawbs.domain.candidate.Candidate" column="candidate_id"/>
            </bag>
            <bag name="blacklistedEmployers" table="job_seeker_to_blacklisted_employer">
                <key column="job_seeker_id"/>
                <many-to-many class="jawbs.domain.employer.Employer" column="blacklisted_employer_id"/>
            </bag>
            <bag name="resumes" table="resume" inverse="true" cascade="all-delete-orphan">
                <key column="job_seeker_id"/>
                <one-to-many class="jawbs.domain.resume.Resume" />
            </bag>
        </subclass>
{code}

And here is the mapping of the subclass of resume:

{code}
        <subclass name="jawbs.domain.resume.JobSeekerResume" discriminator-value="JOBSEEKER">
            <many-to-one name="jobSeeker" class="jawbs.domain.jobseeker.JobSeeker"
                         column="job_seeker_id" />
        </subclass>
{code}

If you need any more assistance, please let me know. This is a critical blocker to getting a test to pass, this is most likely 100% fine.

Thanks

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        


More information about the hibernate-issues mailing list