HHH-8167 - Adding @NotNull to a @ManyToOne association with @JoinColumnsOrFormulas logs a ClassCastException

This commit is contained in:
Steve Ebersole 2013-04-11 14:28:22 -05:00
parent 53f7b73adb
commit c259e157b0
14 changed files with 216 additions and 56 deletions

View File

@ -4890,7 +4890,7 @@ Changes in version 0.9.5 (8.2.2002)
* fixed potential bug related to cacheing of compiled queries
* major rewrite of code relating to O-R mappings
* Session.copy() and Session.equals() as convenience for users
* fixed repeated invocations of hasNext() on iterator + iterators now always work with distinct SQL resultsets
* fixed repeated invocations of hasNext() on iterator + wrappedIterators now always work with distinct SQL resultsets
* McKoi dialect was contributed by Gabe Hicks
Changes in version 0.9.4 (29.1.2002)

View File

@ -26,6 +26,8 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.jboss.logging.Logger;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.MappingException;
@ -33,12 +35,15 @@ import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.SimpleValue;
/**
* @author Emmanuel Bernard
*/
public class CopyIdentifierComponentSecondPass implements SecondPass {
private static final Logger log = Logger.getLogger( CopyIdentifierComponentSecondPass.class );
private final String referencedEntityName;
private final Component component;
private final Mappings mappings;
@ -113,7 +118,7 @@ public class CopyIdentifierComponentSecondPass implements SecondPass {
final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue();
value.setTypeName( referencedValue.getTypeName() );
value.setTypeParameters( referencedValue.getTypeParameters() );
final Iterator<Column> columns = referencedValue.getColumnIterator();
final Iterator<Selectable> columns = referencedValue.getColumnIterator();
if ( joinColumns[0].isNameDeferred() ) {
joinColumns[0].copyReferencedStructureAndCreateDefaultJoinColumns(
@ -124,7 +129,12 @@ public class CopyIdentifierComponentSecondPass implements SecondPass {
else {
//FIXME take care of Formula
while ( columns.hasNext() ) {
Column column = columns.next();
final Selectable selectable = columns.next();
if ( ! Column.class.isInstance( selectable ) ) {
log.debug( "Encountered formula definition; skipping" );
continue;
}
final Column column = (Column) selectable;
final Ejb3JoinColumn joinColumn;
String logicalColumnName = null;
if ( isExplicitReference ) {

View File

@ -60,6 +60,7 @@ import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.SingleTableSubclass;
/**
@ -156,6 +157,7 @@ class TypeSafeActivator {
}
applyRelationalConstraints(
factory,
activationContext.getConfiguration().createMappings().getClasses().values(),
properties,
activationContext.getServiceRegistry().getService( JdbcServices.class ).getDialect()
@ -163,8 +165,11 @@ class TypeSafeActivator {
}
@SuppressWarnings( {"UnusedDeclaration"})
public static void applyRelationalConstraints(Collection<PersistentClass> persistentClasses, Properties properties, Dialect dialect) {
ValidatorFactory factory = getValidatorFactory( properties );
public static void applyRelationalConstraints(
ValidatorFactory factory,
Collection<PersistentClass> persistentClasses,
Properties properties,
Dialect dialect) {
Class<?>[] groupsArray = new GroupsPerOperation( properties ).get( GroupsPerOperation.Operation.DDL );
Set<Class<?>> groups = new HashSet<Class<?>>( Arrays.asList( groupsArray ) );
@ -305,14 +310,23 @@ class TypeSafeActivator {
private static boolean applyNotNull(Property property, ConstraintDescriptor<?> descriptor) {
boolean hasNotNull = false;
if ( NotNull.class.equals( descriptor.getAnnotation().annotationType() ) ) {
// single table inheritance should not be forced to null due to shared state
if ( !( property.getPersistentClass() instanceof SingleTableSubclass ) ) {
//single table should not be forced to null
if ( !property.isComposite() ) { //composite should not add not-null on all columns
@SuppressWarnings( "unchecked" )
Iterator<Column> iter = property.getColumnIterator();
//composite should not add not-null on all columns
if ( !property.isComposite() ) {
final Iterator<Selectable> iter = property.getColumnIterator();
while ( iter.hasNext() ) {
iter.next().setNullable( false );
hasNotNull = true;
final Selectable selectable = iter.next();
if ( Column.class.isInstance( selectable ) ) {
Column.class.cast( selectable ).setNullable( false );
}
else {
LOG.debugf(
"@NotNull was applied to attribute [%s] which is defined (at least partially) " +
"by formula(s); formula portions will be skipped",
property.getName()
);
}
}
}
}

View File

@ -24,46 +24,35 @@
*/
package org.hibernate.internal.util.collections;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* An JoinedIterator is an Iterator that wraps a number of Iterators.
*
* This class makes multiple iterators look like one to the caller.
* When any method from the Iterator interface is called, the JoinedIterator
* will delegate to a single underlying Iterator. The JoinedIterator will
* invoke the Iterators in sequence until all Iterators are exhausted.
* An Iterator implementation that wraps other Iterators, and presents them all as one
* continuous Iterator. When any method from Iterator is called, we delegate to each
* wrapped Iterator in turn until all wrapped Iterators are exhausted.
*
* @author Gavine King
* @author Steve Ebersole
*/
public class JoinedIterator implements Iterator {
public class JoinedIterator<T> implements Iterator<T> {
private Iterator<T>[] wrappedIterators;
private static final Iterator[] ITERATORS = {};
// wrapped iterators
private Iterator[] iterators;
// index of current iterator in the wrapped iterators array
private int currentIteratorIndex;
private Iterator<T> currentIterator;
private Iterator<T> lastUsedIterator;
// the current iterator
private Iterator currentIterator;
// the last used iterator
private Iterator lastUsedIterator;
public JoinedIterator(List iterators) {
this( (Iterator[]) iterators.toArray(ITERATORS) );
@SuppressWarnings("unchecked")
public JoinedIterator(List<Iterator<T>> wrappedIterators) {
this( wrappedIterators.toArray( new Iterator[ wrappedIterators.size() ]) );
}
public JoinedIterator(Iterator[] iterators) {
if( iterators==null )
throw new NullPointerException("Unexpected NULL iterators argument");
this.iterators = iterators;
public JoinedIterator(Iterator<T>... iteratorsToWrap) {
if( iteratorsToWrap == null ) {
throw new NullPointerException( "Iterators to join were null" );
}
public JoinedIterator(Iterator first, Iterator second) {
this( new Iterator[] { first, second } );
this.wrappedIterators = iteratorsToWrap;
}
public boolean hasNext() {
@ -71,7 +60,7 @@ public class JoinedIterator implements Iterator {
return currentIterator.hasNext();
}
public Object next() {
public T next() {
updateCurrentIterator();
return currentIterator.next();
}
@ -85,22 +74,21 @@ public class JoinedIterator implements Iterator {
// call this before any Iterator method to make sure that the current Iterator
// is not exhausted
protected void updateCurrentIterator() {
if ( currentIterator == null ) {
if( iterators.length==0 ) {
currentIterator = EmptyIterator.INSTANCE;
if( wrappedIterators.length == 0 ) {
currentIterator = Collections.emptyIterator();
}
else {
currentIterator = iterators[0];
currentIterator = wrappedIterators[0];
}
// set last used iterator here, in case the user calls remove
// before calling hasNext() or next() (although they shouldn't)
lastUsedIterator = currentIterator;
}
while (! currentIterator.hasNext() && currentIteratorIndex < iterators.length - 1) {
while (! currentIterator.hasNext() && currentIteratorIndex < wrappedIterators.length - 1) {
currentIteratorIndex++;
currentIterator = iterators[currentIteratorIndex];
currentIterator = wrappedIterators[currentIteratorIndex];
}
}

View File

@ -23,6 +23,7 @@
*/
package org.hibernate.mapping;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
@ -365,8 +366,8 @@ public abstract class Collection implements Fetchable, Value, Filterable {
}
}
public Iterator getColumnIterator() {
return EmptyIterator.INSTANCE;
public Iterator<Selectable> getColumnIterator() {
return Collections.emptyIterator();
}
public int getColumnSpan() {

View File

@ -106,7 +106,7 @@ public class Component extends SimpleValue implements MetaAttributable {
}
return n;
}
public Iterator getColumnIterator() {
public Iterator<Selectable> getColumnIterator() {
Iterator[] iters = new Iterator[ getPropertySpan() ];
Iterator iter = getPropertyIterator();
int i=0;

View File

@ -76,7 +76,7 @@ public class OneToMany implements Value {
// no foreign key element of for a one-to-many
}
public Iterator getColumnIterator() {
public Iterator<Selectable> getColumnIterator() {
return associatedClass.getKey().getColumnIterator();
}

View File

@ -22,9 +22,13 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.mapping;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.SQLFunctionRegistry;
/**
* Models the commonality between a column and a formula (computed value).
*/
public interface Selectable {
public String getAlias(Dialect dialect);
public String getAlias(Dialect dialect, Table table);

View File

@ -75,7 +75,8 @@ public class SimpleValue implements KeyValue {
private final Mappings mappings;
private final List columns = new ArrayList();
private final List<Selectable> columns = new ArrayList<Selectable>();
private String typeName;
private Properties identifierGeneratorProperties;
private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY;
@ -132,7 +133,7 @@ public class SimpleValue implements KeyValue {
public int getColumnSpan() {
return columns.size();
}
public Iterator getColumnIterator() {
public Iterator<Selectable> getColumnIterator() {
return columns.iterator();
}
public List getConstraintColumns() {

View File

@ -41,7 +41,7 @@ import org.hibernate.type.Type;
*/
public interface Value extends Serializable {
public int getColumnSpan();
public Iterator getColumnIterator();
public Iterator<Selectable> getColumnIterator();
public Type getType() throws MappingException;
public FetchMode getFetchMode();
public Table getTable();

View File

@ -0,0 +1,42 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.formulajoin;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author Steve Ebersole
*/
@Entity
public class AnnotatedDetail {
@Id
private Integer id;
private String name;
// because otherwise schema export would not know about it...
@Column( name = "detail_domain" )
private String domain;
}

View File

@ -0,0 +1,44 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.formulajoin;
import org.hibernate.cfg.Configuration;
import org.junit.Test;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
/**
* @author Steve Ebersole
*/
public class AnnotatedFormWithBeanValidationNotNullTest extends BaseUnitTestCase {
@Test
@TestForIssue( jiraKey = "HHH-8167" )
public void testAnnotatedFormWithBeanValidationNotNull() {
Configuration cfg = new Configuration();
cfg.addAnnotatedClass( AnnotatedMaster.class ).addAnnotatedClass( AnnotatedDetail.class );
cfg.buildSessionFactory();
}
}

View File

@ -0,0 +1,55 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.formulajoin;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.JoinColumnOrFormula;
import org.hibernate.annotations.JoinColumnsOrFormulas;
import org.hibernate.annotations.JoinFormula;
/**
* @author Steve Ebersole
*/
@Entity
public class AnnotatedMaster {
@Id
private Integer id;
private String name;
@ManyToOne(fetch= FetchType.EAGER, optional=false)
@JoinColumnsOrFormulas({
@JoinColumnOrFormula(formula=@JoinFormula(value="my_domain_key'", referencedColumnName="detail_domain")),
@JoinColumnOrFormula(column=@JoinColumn(name="detail", referencedColumnName="id"))
})
@Fetch(FetchMode.JOIN)
@NotNull
private AnnotatedDetail detail;
}

View File

@ -30,6 +30,8 @@ import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
@ -111,6 +113,5 @@ public class FormulaJoinTest extends BaseCoreFunctionalTestCase {
s.close();
}
}