-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 
Author Message
 Post subject: PersistentEnum migration
PostPosted: Tue May 04, 2004 9:12 pm 
Newbie

Joined: Tue Oct 21, 2003 1:25 pm
Posts: 14
Location: Los Angeles, CA
So I'm undertaking the task of converting our PersistentEnums to Apache's ValuedEnums (the Enums which have an integer), and making associated UserType.

Everything's going pretty good except for a fairly major snag... For thoes who are still a little lost I'll briefly outline the steps before I get to my problem:

First, for each PersistentEnum, extend Apache's ValuedEnums and use their rather nice interface to eliminate the need for the case statement (you also get hashCode and equals for free too).

Then, copy the EnumUserType from the example at the bottom of http://www.hibernate.org/172.html into your project, except convert it to an int/Integer instead of a String.

Make an instance of this class for each Enumeration you want to persist with a no-args constructor which passes in the class type to the super().
For example: If you have an RGBEnum, make an RGBEnumType extends EnumUserType which passes in RGBEnum.class to the super() in the constructor:
Code:
public class RGBEnumType extends EnumUserType{
  public RGBEnumType(){
    super(RGBEnum.class);
  }
}

Add a type='...' for each property in your mappings where you have a PersistentEnum, specifying the EnumUserType for that class.
Again for the RGBEnum example, if it was previously:
Code:
<property name='RGB' column='N_RGB' />

the new mapping would look like:
Code:
<property name='RGB' column='N_RGB' type='eg.example.RGBEnumType' />


Finally, remove PersistentEnum from the 'implements' in your enumerations.

You're now PersistentEnum free, and ready for H2.2

My problem:

I have a query in which I'm doing a where, specifying one of the Enum's as the argument:
Code:
... and job.jobType = eg.example.JobTypeEnum.CAST


JobTypeEnum used to be a PersistentEnum... JobTypeEnum.CAST is still valid, but now Hibernate has no clue how to deal with it (because it no longer implements the PersistentEnum interface).

When I try and do this query I get:

Code:
Could not format constant value to SQL literal: eg.example.JobType.CAST


I understand what's wrong here: Hibernate used to know how to deal with this; it would just call toInt() on it. Now it can't. What do I need to do to make this work again?

I absolutely understand the reasoning behind this change... but what can I do to keep the elegance afforded to me by PersistentEnum in my queries?

Any and all assistance is appreaciated.

-Travis Savo <tsavo@ifilm.com>


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 04, 2004 10:36 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Unfortunately the solution is somewhat inelegant. You need to use parameters in your query, and then set the parameter value using the Hibernate.custom() method.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 04, 2004 10:45 pm 
Newbie

Joined: Tue Oct 21, 2003 1:25 pm
Posts: 14
Location: Los Angeles, CA
Thank you for your reply.

I hear what your saying... but that's very undesirable, because the parameters isn't dynamic: It's always the same for that query, and therefore dosn't need to be bound at runtime.

The other option is to move the 'value' of the enum into the query itself, but this is also undesirable because it breaks the pure object model, and creates an undesirable dependancy: if the enumerations ever change in the class, it's gotta change in the query too. Bad form.

Is there any other solution? It's kinda a sticky problem because without an interface to cast it to, you have no 'clean' way of specifying what piece of information is relevant to Hibernate on that class. And the whole point of this excercise is to remove the dependancy on Hibernate... not invent a new Hibernate interface and dependancy.

I realize the problem may not be solvable in it's current form, but is there an elegant solution that someone might propose?

Thanks in advance.

-Travis Savo <tsavo@ifilm.com>


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 04, 2004 10:50 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Well all I can tell you is that this is currently not possible. I cannot say what the effort might be to change this nor even where all it needs to get changed. But as always, patches are welcome.


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 05, 2004 11:14 am 
Beginner
Beginner

Joined: Mon Mar 22, 2004 10:01 am
Posts: 22
take a look at LiteralType, an example implementation: CurrencyType

it has a method: public String objectToSQLString(Object value)

might be a solution...

- 101


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 05, 2004 3:21 pm 
Newbie

Joined: Tue Oct 21, 2003 1:25 pm
Posts: 14
Location: Los Angeles, CA
Yep.

The problem appears to be in PersistentEnumType. Within this class there's several casts to a PersistentEnum, for the purpose of calling .toInt().

Is it permissible to cast to either a PersistentEnum, OR a ValuedEnum in there? I'm asking because I'm not entirely sure it's the right solution, but it seems like it's better than the current behavior. In the worst case senario, you get a ClassCastException, which is what you get now anyway. Hibernate allready depends on commons-lang, so there no new dependancy being added here...

Would a patch of this nature be considered? Or is there a more appropiate way to solve this problem?

-Travis Savo <tsavo@ifilm.com>


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 05, 2004 6:07 pm 
Newbie

Joined: Tue Oct 21, 2003 1:25 pm
Posts: 14
Location: Los Angeles, CA
So I'm kinda suprised no one's lept outta their seats and said, "No! That's a really bad hack!".

For that matter I'm suprised no one caught my misunderstanding with creating an implementing type for each Enum... Hibernate will do that for you if you just specify type='eg.example.EnumUserType'.

But water under the bridge. He's my patch for the proposed solution which will eliminate all that entirely, and make them work just like PersistentEnums:

First, add the class net.sf.hibernate.type.ValuedEnumType, which is as follows:

Code:
package net.sf.hibernate.type;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import net.sf.hibernate.Hibernate;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.MappingException;

import org.apache.commons.lang.enum.EnumUtils;
import org.apache.commons.lang.enum.ValuedEnum;

/**
* Maps a commons-lang <code>Enum</code> to a Hibernate type.
*/
public class ValuedEnumType extends ImmutableType implements LiteralType {

   private Class enumClass;
   private static final Class[] ENUM_ARG = new Class[] { Class.class, int.class };
   

   public ValuedEnumType(Class enumClass) throws MappingException{
      this.enumClass = enumClass;
   }

   /**
    * (at) see net (dot) sf.hibernate.UserType#sqlTypes()
    */
   public int sqlType() {
      return Types.TINYINT;
   }

   /**
    * (at) see net (dot) sf.hibernate.UserType#returnedClass()
    */
   public Class getReturnedClass() {
      return enumClass;
   }

   /**
    * (at) see net (dot) sf.hibernate.UserType#equals(java.lang.Object,
    * java.lang.Object)
    */
   public boolean equals(Object x, Object y) throws HibernateException {
      if (x == y) {
         return true;
      }
      if (x == null || y == null) {
         return false;
      }
      return Hibernate.STRING.equals(x, y);
   }

   /* (non-Javadoc)
    * @see net.sf.hibernate.type.NullableType#get(java.sql.ResultSet, java.lang.String)
    */
   public Object get(ResultSet rs, String name) throws HibernateException, SQLException {
      int enumCode = ((Integer) Hibernate.INTEGER.nullSafeGet(rs, name)).intValue();
      return EnumUtils.getEnum(enumClass, enumCode);
   }
   
   /**
    * @see net.sf.hibernate.UserType#nullSafeSet(java.sql.PreparedStatement,
    *      java.lang.Object, int)
    */
   public void set(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
      // make sure the received value is of the right type
      if ((value != null) && !getReturnedClass().isAssignableFrom(value.getClass())) {
         throw new IllegalArgumentException("Received value is not a [" + getReturnedClass().getName() + "] but [" + value.getClass() + "]");
      }
      ValuedEnum enum = (ValuedEnum) value;
      // convert the enum into its persistence format
      int enumCode = enum.getValue();
      // set the value into the resultset
      st.setInt(index, enumCode);
   }


   /* (non-Javadoc)
    * @see net.sf.hibernate.type.NullableType#toString(java.lang.Object)
    */
   public String toString(Object value) throws HibernateException {
      return Integer.toString( ( (ValuedEnum) value ).getValue() );
   }

   /* (non-Javadoc)
    * @see net.sf.hibernate.type.NullableType#fromStringValue(java.lang.String)
    */
   public Object fromStringValue(String xml) throws HibernateException {
      return getInstance( new Integer(xml) );
   }

   public Object getInstance(Integer code) throws HibernateException {
      return EnumUtils.getEnum(enumClass, code.intValue());
   }
   /* (non-Javadoc)
    * @see net.sf.hibernate.type.LiteralType#objectToSQLString(java.lang.Object)
    */
   public String objectToSQLString(Object value) throws Exception {
      return Integer.toString(((ValuedEnum) value).getValue());
   }

   /* (non-Javadoc)
    * @see net.sf.hibernate.type.Type#getName()
    */
   public String getName() {
      return enumClass.getName();
   }
}


Next, add the following method to net.sf.Hibernate:

Code:
   public static Type valuedEnum(Class enumClass) throws MappingException {
      return new ValuedEnumType(enumClass);
   }


Finally, add the new type to the net.sf.hibernate.type.TypeFactory. Ultimately this will replace PersistenEnum... The following starts around line 167:
Code:
            else if ( Lifecycle.class.isAssignableFrom(typeClass) ) {
               type = Hibernate.entity(typeClass);
            }
/* !!! Add these 3 lines */
            else if ( ValuedEnum.class.isAssignableFrom(typeClass) ) {
               type = Hibernate.valuedEnum(typeClass);
            }
/* !!! This goes away in 2.2 */
            else if ( PersistentEnum.class.isAssignableFrom(typeClass) ) {
               type = Hibernate.enum(typeClass);
            }


And assuming your Enum's implement ValuedType, everything will work as normal again.

Any problems with doing this? Any chance we can get this into the next release?

-Travis Savo <tsavo@ifilm.com>


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 05, 2004 6:23 pm 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
It will get lost on the forum quickly, either open a JIRA issue or put it on a Wiki page.

_________________
JAVA PERSISTENCE WITH HIBERNATE
http://jpwh.org
Get the book, training, and consulting for your Hibernate team.


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 05, 2004 6:32 pm 
Newbie

Joined: Tue Oct 21, 2003 1:25 pm
Posts: 14
Location: Los Angeles, CA
Sure thing!

Here's the link to the bug:

http://opensource.atlassian.com/project ... wse/HB-934

-Travis Savo


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 10, 2004 3:39 pm 
Newbie

Joined: Tue Oct 21, 2003 1:25 pm
Posts: 14
Location: Los Angeles, CA
I'm still looking for a viable solution to this.

For a refresher: By eliminating the PersistentEnum interface and not replacing it with something else you can't use Enumerations in queries in a static way. You MUST bind them dynamically, despite the fact they work just fine statically if they're PersistentEnums (which are going away), and are really static elements of the query.

Is it possible to specify the Enum in a mapping so I can associate a UserType with it? This would solve the problem...

Essentially I want to do something like this:

Code:
<class name="eg.example.MyEnum" type="eg.example.MyEnumUserType" />


So when the WhereParser will know to use MyEnumUserType when it looks for the UserType for that type.

Essentially what I've done is replaced table with type... and I realize this breaks the model in very ugly ways, but despite repeated questions, no one has been able to say what the new solution for using Enums statically in queries will look like.

Can someone suggest what the right way of doing this might be? I'de be happy to make a patch if I knew what the right way to model this relationship was (because just 'type="..."' in a id/property mapping simply isn't sufficent for obvious reasons).

-Travis Savo <tsavo@ifilm.com>


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.