OpenJPA
  1. OpenJPA
  2. OPENJPA-386

org.apache.openjpa.meta.ClassMetaData.validateAppIdClass() does not take @MappedSuperclass into account

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 0.9.7, 1.0.0, 1.0.2, 1.1.0, 1.2.1
    • Fix Version/s: 1.1.1, 1.3.0, 2.0.0-M3
    • Component/s: kernel
    • Labels:
      None
    • Environment:
      BEA WebLogic 10.0 under Windows XP SP1
    • Patch Info:
      Patch Available

      Description

      We have a @MappedSuperclass that is the base for all of our entity bean classes. This class contains the @Version (and @Column for the version field) that all of our entity beans use. But, in ClassMetaData.validateAppIdClass(), if an entity bean class has a superclass, and the entity bean has an @IdClass, it expects that superclass to have an @IdClass also. All of our entity beans have an @IdClass (even if they only have a single part key), but our @MappedSuperclass does not have an @IdClass. This scenario works under JBoss 4.2.0 and Sun AS 9, but produces a NullPointerException in OpenJPA at:

      if (_super != null) {
      // concrete superclass oids must match or be parent of ours
      ClassMetaData sup = getPCSuperclassMetaData();
      if (!sup.getObjectIdType().isAssignableFrom(_objectId)) // <--- NullPointerException here

      1. OPENJPA-386_1.0.x.patch
        11 kB
        Jody Grassel
      2. OPENJPA-386_1.1.x.patch
        12 kB
        Jody Grassel
      3. OPENJPA-386_1.2.x.patch
        9 kB
        Jody Grassel
      4. OPENJPA-386_1.3.x.patch
        9 kB
        Jody Grassel
      5. OPENJPA-386_trunk.patch
        9 kB
        Jody Grassel

        Activity

        Hide
        David Ezzio added a comment -

        Applied Jody's patch to 1.1.x branch at rev 889476.

        Show
        David Ezzio added a comment - Applied Jody's patch to 1.1.x branch at rev 889476.
        Hide
        Michael Dick added a comment -

        Thanks for the patch Jody. Patch applied to 1.3.x, and trunk.

        Show
        Michael Dick added a comment - Thanks for the patch Jody. Patch applied to 1.3.x, and trunk.
        Hide
        Jody Grassel added a comment -

        The problem was how ClassMetaData.hasConcretePCSuperclass() handled mapped superclasses. Let's say we have the following classes:

        @MappedSuperclass
        public class MSC

        @Entity
        @IdClass(MyIdClass.class)
        public class EntityA extends MSC

        (Note that the @MappedSuperclass type "MSC" is not abstract, which is perfectly legal with the JPA Spec.)

        Now, when an entity extending the MSC is annotated with @IdClass, the metadata processing notes that there are PC-enhanced superclasses, and needs to analyze them to check if they declare any identity elements, in order to ensure that the @IdClass specified will address them. It uses this method in ClassMetaData to check if it has a PC superclass that is concrete (effectively, any entity class types that are further up the inheritance tree.:

        /**

        • Return true if this class has a concrete persistent superclass.
          */
          private boolean hasConcretePCSuperclass() { if (_super == null) return false; if (!Modifier.isAbstract(_super.getModifiers()))) return true; return getPCSuperclassMetaData().hasConcretePCSuperclass(); }

        The line: if (!Modifier.isAbstract(_super.getModifiers()))) is responsible for detecting @MappedSuperclasses, however it relies exclusively on the class being abstract, something that, while recommended, is not required by the JPA Spec. So it will incorrectly treat a @MappedSuperclass as a regular entity, resulting in this Exception:

        org.apache.openjpa.util.MetaDataException: Field "testcase.EntityA.id" cannot be a primary key. Primary key fields can only be declared in base persistent classes that also declare their identity type- to be "application".
        at org.apache.openjpa.meta.ClassMetaData.validateNoPKFields(ClassMetaData.java:2025)
        at org.apache.openjpa.meta.ClassMetaData.validateAppIdClass(ClassMetaData.java:1905)
        at org.apache.openjpa.meta.ClassMetaData.validateIdentity(ClassMetaData.java:1851)
        at org.apache.openjpa.meta.ClassMetaData.validateMeta(ClassMetaData.java:1768)
        at org.apache.openjpa.meta.ClassMetaData.resolve(ClassMetaData.java:1641)
        ...

        The solution was to simply add another check for the @MappedSuperclass. This is already done with the ClassMetaData.isAbstract() method in trunk (which is set true if a class type explicitly is annotated with @MappedSuperclass, or is defined as a mapped-superclass by ORM XML), which was brought down to 1.2.x and 1.3.x in OPENJPA-1061. With the following change:

        private boolean hasConcretePCSuperclass()

        { if (_super == null) return false; if (!Modifier.isAbstract(_super.getModifiers()) && (!getPCSuperclassMetaData().isAbstract())) return true; return getPCSuperclassMetaData().hasConcretePCSuperclass(); }

        With the above change, a @MappedSuperclass can now be identified whether or not its class type is actually a Java abstract class or not.

        Show
        Jody Grassel added a comment - The problem was how ClassMetaData.hasConcretePCSuperclass() handled mapped superclasses. Let's say we have the following classes: @MappedSuperclass public class MSC @Entity @IdClass(MyIdClass.class) public class EntityA extends MSC (Note that the @MappedSuperclass type "MSC" is not abstract, which is perfectly legal with the JPA Spec.) Now, when an entity extending the MSC is annotated with @IdClass, the metadata processing notes that there are PC-enhanced superclasses, and needs to analyze them to check if they declare any identity elements, in order to ensure that the @IdClass specified will address them. It uses this method in ClassMetaData to check if it has a PC superclass that is concrete (effectively, any entity class types that are further up the inheritance tree.: /** Return true if this class has a concrete persistent superclass. */ private boolean hasConcretePCSuperclass() { if (_super == null) return false; if (!Modifier.isAbstract(_super.getModifiers()))) return true; return getPCSuperclassMetaData().hasConcretePCSuperclass(); } The line: if (!Modifier.isAbstract(_super.getModifiers()))) is responsible for detecting @MappedSuperclasses, however it relies exclusively on the class being abstract, something that, while recommended, is not required by the JPA Spec. So it will incorrectly treat a @MappedSuperclass as a regular entity, resulting in this Exception: org.apache.openjpa.util.MetaDataException: Field "testcase.EntityA.id" cannot be a primary key. Primary key fields can only be declared in base persistent classes that also declare their identity type- to be "application". at org.apache.openjpa.meta.ClassMetaData.validateNoPKFields(ClassMetaData.java:2025) at org.apache.openjpa.meta.ClassMetaData.validateAppIdClass(ClassMetaData.java:1905) at org.apache.openjpa.meta.ClassMetaData.validateIdentity(ClassMetaData.java:1851) at org.apache.openjpa.meta.ClassMetaData.validateMeta(ClassMetaData.java:1768) at org.apache.openjpa.meta.ClassMetaData.resolve(ClassMetaData.java:1641) ... The solution was to simply add another check for the @MappedSuperclass. This is already done with the ClassMetaData.isAbstract() method in trunk (which is set true if a class type explicitly is annotated with @MappedSuperclass, or is defined as a mapped-superclass by ORM XML), which was brought down to 1.2.x and 1.3.x in OPENJPA-1061 . With the following change: private boolean hasConcretePCSuperclass() { if (_super == null) return false; if (!Modifier.isAbstract(_super.getModifiers()) && (!getPCSuperclassMetaData().isAbstract())) return true; return getPCSuperclassMetaData().hasConcretePCSuperclass(); } With the above change, a @MappedSuperclass can now be identified whether or not its class type is actually a Java abstract class or not.
        Hide
        Tamas Sandor added a comment -

        Matthew,
        We have the same problem (and situation for storing @Version). It's great that I found this entry.

        Here's a workaround that worked for me:
        @MappedSuperclass
        @IdClass(Object.class)

        This way PCEnhancer will enhance the superclassed entities with their special @IdClass().

        However, normally it should work without this extra line (Hibernate does not need that).
        Please vote for this bug to be solved in the next release.

        Show
        Tamas Sandor added a comment - Matthew, We have the same problem (and situation for storing @Version). It's great that I found this entry. Here's a workaround that worked for me: @MappedSuperclass @IdClass(Object.class) This way PCEnhancer will enhance the superclassed entities with their special @IdClass(). However, normally it should work without this extra line (Hibernate does not need that). Please vote for this bug to be solved in the next release.
        Hide
        Chris Arnett added a comment -

        Any updates on this? We're having the same problem. These are legacy tables and we don't have the luxury of changing the schema. We also can't use an EmbeddedId (which seems to work in this case) because one of the fields is generated while the other is not (an unsupported case for EmbeddedId).

        Unless this is fixed in the Feb 15th release, we'll probably have to override WL10's JPA implementation with Hibernate (if that's even possible).

        Show
        Chris Arnett added a comment - Any updates on this? We're having the same problem. These are legacy tables and we don't have the luxury of changing the schema. We also can't use an EmbeddedId (which seems to work in this case) because one of the fields is generated while the other is not (an unsupported case for EmbeddedId). Unless this is fixed in the Feb 15th release, we'll probably have to override WL10's JPA implementation with Hibernate (if that's even possible).

          People

          • Assignee:
            Jody Grassel
            Reporter:
            Matthew L. Schwickerath
          • Votes:
            3 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development