From 79b10cc810b2350745dd9c8b655e8cbd6b5f64d9 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 21 Jan 2010 19:37:01 +0000 Subject: [PATCH] HHH-4552 - Support generated value within composite keys git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18601 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- .../def/DefaultFlushEntityEventListener.java | 13 +- ...ompositeNestedGeneratedValueGenerator.java | 96 +++++++++++ .../java/org/hibernate/mapping/Component.java | 153 +++++++++++++++++- .../org/hibernate/mapping/SimpleValue.java | 6 +- 4 files changed, 257 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java diff --git a/core/src/main/java/org/hibernate/event/def/DefaultFlushEntityEventListener.java b/core/src/main/java/org/hibernate/event/def/DefaultFlushEntityEventListener.java index 0d6b2397da..eab1b31f39 100755 --- a/core/src/main/java/org/hibernate/event/def/DefaultFlushEntityEventListener.java +++ b/core/src/main/java/org/hibernate/event/def/DefaultFlushEntityEventListener.java @@ -38,6 +38,7 @@ import org.hibernate.classic.Validatable; import org.hibernate.engine.EntityEntry; import org.hibernate.engine.EntityKey; import org.hibernate.engine.Nullability; +import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.Status; import org.hibernate.engine.Versioning; @@ -62,8 +63,12 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener /** * make sure user didn't mangle the id */ - public void checkId(Object object, EntityPersister persister, Serializable id, EntityMode entityMode) - throws HibernateException { + public void checkId( + Object object, + EntityPersister persister, + Serializable id, + EntityMode entityMode, + SessionFactoryImplementor factory) throws HibernateException { if ( id != null && id instanceof DelayedPostInsertIdentifier ) { // this is a situation where the entity id is assigned by a post-insert generator @@ -77,7 +82,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener if (id==null) { throw new AssertionFailure("null id in " + persister.getEntityName() + " entry (don't flush the Session after an exception occurs)"); } - if ( !persister.getIdentifierType().isEqual(id, oid, entityMode) ) { + if ( !persister.getIdentifierType().isEqual( id, oid, entityMode, factory ) ) { throw new HibernateException( "identifier of an instance of " + persister.getEntityName() + @@ -184,7 +189,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener values = loadedState; } else { - checkId( entity, persister, entry.getId(), entityMode ); + checkId( entity, persister, entry.getId(), entityMode, session.getFactory() ); // grab its current state values = persister.getPropertyValues( entity, entityMode ); diff --git a/core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java b/core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java new file mode 100644 index 0000000000..c02b1c8ec4 --- /dev/null +++ b/core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java @@ -0,0 +1,96 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2010, 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.id; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.hibernate.HibernateException; +import org.hibernate.engine.SessionImplementor; + +/** + * For composite identifiers, defines a number of "nested" generations that + * need to happen to "fill" the identifier property(s). + *

+ * This generator is used implicitly for all composite identifier scenarios if an + * explicit generator is not in place. So it make sense to discuss the various + * potential scenarios:

+ *

+ * Most of the grunt work is done in {@link org.hibernate.mapping.Component}. + * + * @author Steve Ebersole + */ +public class CompositeNestedGeneratedValueGenerator implements IdentifierGenerator, Serializable { + public static interface GenerationContextLocator { + public Serializable locateGenerationContext(SessionImplementor session, Object incomingObject); + } + + public static interface GenerationPlan { + public void execute(SessionImplementor session, Object incomingObject); + } + + private final GenerationContextLocator generationContextLocator; + private List generationPlans = new ArrayList(); + + public CompositeNestedGeneratedValueGenerator(GenerationContextLocator generationContextLocator) { + this.generationContextLocator = generationContextLocator; + } + + public void addGeneratedValuePlan(GenerationPlan plan) { + generationPlans.add( plan ); + } + + public Serializable generate(SessionImplementor session, Object object) throws HibernateException { + final Serializable context = generationContextLocator.locateGenerationContext( session, object ); + + Iterator itr = generationPlans.iterator(); + while ( itr.hasNext() ) { + final GenerationPlan plan = (GenerationPlan) itr.next(); + plan.execute( session, context ); + } + + return context; + } + +} diff --git a/core/src/main/java/org/hibernate/mapping/Component.java b/core/src/main/java/org/hibernate/mapping/Component.java index 0b38f227e5..13c0b6d3d4 100644 --- a/core/src/main/java/org/hibernate/mapping/Component.java +++ b/core/src/main/java/org/hibernate/mapping/Component.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * Copyright (c) 2010, 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 Middleware LLC. + * 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 @@ -20,17 +20,30 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - * */ package org.hibernate.mapping; +import java.io.Serializable; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.hibernate.EntityMode; +import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.id.CompositeNestedGeneratedValueGenerator; +import org.hibernate.id.IdentifierGenerator; +import org.hibernate.id.factory.IdentifierGeneratorFactory; +import org.hibernate.property.Getter; +import org.hibernate.property.PropertyAccessor; +import org.hibernate.property.Setter; import org.hibernate.tuple.component.ComponentMetamodel; import org.hibernate.type.ComponentType; import org.hibernate.type.EmbeddedComponentType; @@ -41,10 +54,11 @@ import org.hibernate.util.ReflectHelper; /** * The mapping for a component, composite element, * composite identifier, etc. + * * @author Gavin King + * @author Steve Ebersole */ public class Component extends SimpleValue implements MetaAttributable { - private ArrayList properties = new ArrayList(); private String componentClassName; private boolean embedded; @@ -295,4 +309,135 @@ public class Component extends SimpleValue implements MetaAttributable { return getClass().getName() + '(' + properties.toString() + ')'; } + private IdentifierGenerator builtIdentifierGenerator; + + public IdentifierGenerator createIdentifierGenerator( + IdentifierGeneratorFactory identifierGeneratorFactory, + Dialect dialect, + String defaultCatalog, + String defaultSchema, + RootClass rootClass) throws MappingException { + if ( builtIdentifierGenerator == null ) { + builtIdentifierGenerator = buildIdentifierGenerator( + identifierGeneratorFactory, + dialect, + defaultCatalog, + defaultSchema, + rootClass + ); + } + return builtIdentifierGenerator; + } + + private IdentifierGenerator buildIdentifierGenerator( + IdentifierGeneratorFactory identifierGeneratorFactory, + Dialect dialect, + String defaultCatalog, + String defaultSchema, + RootClass rootClass) throws MappingException { + final boolean hasCustomGenerator = ! DEFAULT_ID_GEN_STRATEGY.equals( getIdentifierGeneratorStrategy() ); + if ( hasCustomGenerator ) { + return super.createIdentifierGenerator( + identifierGeneratorFactory, dialect, defaultCatalog, defaultSchema, rootClass + ); + } + + final Class entityClass = rootClass.getMappedClass(); + final Class attributeDeclarer; // what class is the declarer of the composite pk attributes + CompositeNestedGeneratedValueGenerator.GenerationContextLocator locator; + + // IMPL NOTE : See the javadoc discussion on CompositeNestedGeneratedValueGenerator wrt the + // various scenarios for which we need to account here + if ( isEmbedded() ) { + // we have the "straight up" embedded (again the hibernate term) component identifier + attributeDeclarer = entityClass; + } + else if ( rootClass.getIdentifierProperty() != null ) { + // we have the "@EmbeddedId" / case + attributeDeclarer = resolveComponentClass(); + } + else { + // we have the @IdClass / case + attributeDeclarer = resolveComponentClass(); + } + + locator = new StandardGenerationContextLocator( rootClass.getEntityName() ); + final CompositeNestedGeneratedValueGenerator generator = new CompositeNestedGeneratedValueGenerator( locator ); + + Iterator itr = getPropertyIterator(); + while ( itr.hasNext() ) { + final Property property = (Property) itr.next(); + if ( property.getValue().isSimpleValue() ) { + final SimpleValue value = (SimpleValue) property.getValue(); + + if ( DEFAULT_ID_GEN_STRATEGY.equals( value.getIdentifierGeneratorStrategy() ) ) { + // skip any 'assigned' generators, they would have been handled by + // the StandardGenerationContextLocator + continue; + } + + final IdentifierGenerator valueGenerator = value.createIdentifierGenerator( + identifierGeneratorFactory, + dialect, + defaultCatalog, + defaultSchema, + rootClass + ); + final Setter injector = property.getPropertyAccessor( attributeDeclarer ) + .getSetter( attributeDeclarer, property.getName() ); + generator.addGeneratedValuePlan( + new ValueGenerationPlan( + property.getName(), + valueGenerator, + injector + ) + ); + } + } + return generator; + } + + private Class resolveComponentClass() { + try { + return getComponentClass(); + } + catch ( Exception e ) { + return null; + } + } + + public static class StandardGenerationContextLocator + implements CompositeNestedGeneratedValueGenerator.GenerationContextLocator { + private final String entityName; + + public StandardGenerationContextLocator(String entityName) { + this.entityName = entityName; + } + + public Serializable locateGenerationContext(SessionImplementor session, Object incomingObject) { + return session.getEntityPersister( entityName, incomingObject ) + .getIdentifier( incomingObject, session.getEntityMode() ); + } + } + + public static class ValueGenerationPlan implements CompositeNestedGeneratedValueGenerator.GenerationPlan { + private final String propertyName; + private final IdentifierGenerator subGenerator; + private final Setter injector; + + public ValueGenerationPlan( + String propertyName, + IdentifierGenerator subGenerator, + Setter injector) { + this.propertyName = propertyName; + this.subGenerator = subGenerator; + this.injector = injector; + } + + public void execute(SessionImplementor session, Object incomingObject) { + final Object generatedValue = subGenerator.generate( session, incomingObject ); + injector.set( incomingObject, generatedValue, session.getFactory() ); + } + } + } diff --git a/core/src/main/java/org/hibernate/mapping/SimpleValue.java b/core/src/main/java/org/hibernate/mapping/SimpleValue.java index 70a4aede18..da0f6285ff 100644 --- a/core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -46,11 +46,12 @@ import org.hibernate.util.ReflectHelper; * @author Gavin King */ public class SimpleValue implements KeyValue { + public static final String DEFAULT_ID_GEN_STRATEGY = "assigned"; private final List columns = new ArrayList(); private String typeName; private Properties identifierGeneratorProperties; - private String identifierGeneratorStrategy = "assigned"; + private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY; private String nullValue; private Table table; private String foreignKeyName; @@ -124,8 +125,7 @@ public class SimpleValue implements KeyValue { Dialect dialect, String defaultCatalog, String defaultSchema, - RootClass rootClass) - throws MappingException { + RootClass rootClass) throws MappingException { Properties params = new Properties();