-->
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.  [ 8 posts ] 
Author Message
 Post subject: Interface-mappings for AOP mixin persistence
PostPosted: Wed May 19, 2004 10:27 am 
Beginner
Beginner

Joined: Sat Jan 17, 2004 5:35 am
Posts: 25
Location: Birmingham, UK
I posted this to Hibernate Beginners, but retrospectively think it's perhaps more suited here:

I'm currently trying to integrate Hibernate into an AOP system that uses Mixins/Introductions to add dynamic functionality to domain objects at runtime.

A rather contrived example should demonstrate the basic scenario:

Code:
public interface Document
{
   public Long getId();
   public void setId(Long id);
   public String getData();
   public void setData(String data);
}

public class DocumentImpl implements Document
{
   private Long id;
   private String data;
   public Long getId() {return id;}
   public void setId(Long id) {this.id = id;}
   public String getData() {return data;}
   public void setData(String data) {this.data = data;}
}

public interface Nameable
{
   public String getName();
   public void setName(String name);
}

public class NameableMixin implements Nameable
{
   private String name;
   public String getName() {return name;}
   public void setName(String name) {this.name = name;}
}


So NameableMixin is introduced to DocumentImpl at runtime to produce the actual class used within the system. Note that this class only exists at runtime, and as thus doesn't have a fully-qualified classname that Hibernate can use. Also, as a consequence of using AOP to introduce the classes, the final aspectised class proxies an instance of DocumentImpl by extending java.lang.reflect.Proxy and implementing Document and Nameable.

Now the problem is how to persist such an entity with Hibernate. I shall firstly detail how I have achieved this at present, and then propose a few changes to Hibernate that would improve the implementation.

To allow Hibernate to recognise the proxy class within it's methods, an Interceptor needs to be written to unwrap the proxy, e.g.

Code:
public class ProxyInterceptor implements Interceptor
{
   public String getEntityName(Object object) throws CallbackException
   {
       return (object instanceof Proxy) ? ((Proxy) object).getProxiedClass().getName() : null;
   }
   ...
}


This allows Hibernate to see the Document+Nameable proxy class as the proxied DocumentImpl instance. A simple mapping for DocumentImpl is then added to the Hibernate configuration:

Code:
<class name="DocumentImpl">
   <id name="id">
      <generator class="native"/>
   </id>
   <property name="data"/>
</class>


The first problem here is that Hibernate correctly expects the instance being persisted to extend DocumentImpl, as we have told it so within our Interceptor.getEntityName() method, but it actually wraps the DocumentImpl instance by extending java.lang.reflect.Proxy. This means that the BasicPropertyAccessor reflection will fail. To overcome this, we can write a custom PropertyAccessor:

Code:
public class DocumentPropertyAccessor extends BasicPropertyAccessor
{
   public Getter getGetter(Class klass, String propertyName) throws PropertyNotFoundException
   {
      return super.getGetter(Document.class, propertyName);
   }
   public Setter getSetter(Class klass, String propertyName) throws PropertyNotFoundException
   {
      return super.getSetter(Document.class, propertyName);
   }
}


And then use this in our mapping file:

Code:
<class name="DocumentImpl">
   <id name="id" access="DocumentPropertyAccessor">
      <generator class="native"/>
   </id>
   <property name="data" access="DocumentPropertyAccessor"/>
</class>


So now the object's properties are accessed through the Document interface methods, rather than the DocumentImpl methods which are inaccessible to the proxy instance. We could have achieved this by declaring the mapping on the Document interface rather than the DocumentImpl class, but then the ProxyInterceptor would have to know which of the interfaces implemented by the proxy should be used as the entity name.

DocumentImpl proxied instances are now persistable via Hibernate, but are still missing the additional fields provided by the NameableMixin. To enable Hibernate to persist these along with the Document fields, we need to add the additional Nameable properties to the DocumentImpl mapping. This can be easily achieved by adding the following property to DocumentImpl.hbm.xml, but is not ideal:

Code:
<property name="name" access="NameablePropertyAccessor"/>


Where NameablePropertyAccessor is similar to DocumentPropertyAccessor, but delegates to Nameable methods instead. The problem with this solution is that the Document.hbm.xml references Nameable-specific classes and properties, which defeats the entire point of AOP's loose-coupling.

An alternative is to add this extra property mapping to the Hibernate configuration at runtime - the same time as the mixins are introduced to the domain objects. This resultant code is not pretty, but basically adds this property to the DocumentImpl mapping in the Configuration object in much the same way as Binder does from the XML. Although, the advantage of this method is that the mixin mappings can be dynamically applied at runtime to any number of persistable entities, whilst keeping the loose-coupling as required.

Hibernate can obviously cope perfectly well with persisting these type of objects internally once the configuration has been set up correctly. It is the configuration that I feel could benefit from a few tweaks to provide for interface-based persistence.

So the question is, how feasible would it be to implement interface-mappings to compliment class-mappings? The idea being that, in the above example, the following two interface mappings could replace the previous cumbersome configuration:

Code:
<interface name="Document">
   <id name="id">
      <generator class="native"/>
   </id>
   <property name="data"/>
</interface>

<interface name="Nameable">
    <property name="name"/>
</interface>


Interface mappings would be very similar to subclass mappings, but they don't extend a class. This allows the Nameable interface mapping to omit the id mapping, which would otherwise be required if it were a class mapping.

Thus when Hibernate is asked to persist an object, it would firstly look for class mappings of the object's class, as it does presently. Failing that, as in our proxy example, it would examine the interfaces implemented by the object's class. So for our Document+Nameable proxy, it would find the Document and Nameable interface mappings and combine them to proceed as normal.

This being my first delve into the Hibernate source, I am blissfully unaware of any consequences that such an addition would have, but it seems to me the neatest solution for aspect-based architectures. Is anyone aware of a better solution for this problem? If not, can anyone see any fundamental problems with interface-mappings?

I would be most interested in any feedback.

Cheers,

Mark


Top
 Profile  
 
 Post subject: getEntityName?
PostPosted: Thu May 20, 2004 8:40 pm 
Newbie

Joined: Tue Mar 30, 2004 12:23 pm
Posts: 14
Hmm... I'm using CGLIB to dynamically create a proxy class and have taken some effort to ensure that Hibernate never gets ahold of one of my proxies. It would be a lot easier if I could just create an Interceptor that resolves the class name properly.

What puzzles me is that the Interceptor interface doesn't have a getEntityName method, or anything similar...

http://www.hibernate.org/hib_docs/api/net/sf/hibernate/Interceptor.html

Is there something I'm missing?

(Sorry, I don't have any additional input regarding persisting based on Interfaces... we're using concrete classes with Hibernate.)

Kevin


Top
 Profile  
 
 Post subject:
PostPosted: Fri May 21, 2004 7:20 am 
Beginner
Beginner

Joined: Sat Jan 17, 2004 5:35 am
Posts: 25
Location: Birmingham, UK
Sorry, I should have mentioned I was using the latest CVS version under the v22branch. In 2.2 the Interceptor interface has a couple of extra methods:

Code:
/**
* Get the entity name for a persistent or transient instance
* @param object an entity instance
* @return the name of the entity
*/
public String getEntityName(Object object) throws CallbackException;
   
/**
* Get a fully loaded entity instance that is cached externally
* @param entityName the name of the entity
* @param id the instance identifier
* @return a fully initialized entity
* @throws CallbackException
*/
public Object getEntity(String entityName, Serializable id) throws CallbackException;


Checkout and compile this branch from CVS to have a play.

Mark


Top
 Profile  
 
 Post subject: I had checked cvs... just not the right version
PostPosted: Fri May 21, 2004 9:21 am 
Newbie

Joined: Tue Mar 30, 2004 12:23 pm
Posts: 14
Thanks for the pointer. I had checked cvs, but I didn't realize that they don't do their work on the HEAD. I see the 2.2 branch with the getEntityName method in the interceptor. Just what I need!

The project I'm working on is due July 31st, so I can only reasonably go with 2.2 as long as 2.2 will be out in that timeframe... It's great to see this feature though!

Kevin


Top
 Profile  
 
 Post subject: sigh... 2.2 is hibernate 3
PostPosted: Fri May 21, 2004 9:29 am 
Newbie

Joined: Tue Mar 30, 2004 12:23 pm
Posts: 14
Gavin's blog says that 2.2 is Hibernate 3, and there are some big things planned. It looks like I might not get this feature for a while, and I'm not comfortable running the CVS version for a production system.

Oh well. I wonder how hard it would be for me to merge that one change in...

Kevin


Top
 Profile  
 
 Post subject:
PostPosted: Fri May 21, 2004 9:31 am 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
http://www.hibernate.org/About/RoadMap

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


Top
 Profile  
 
 Post subject:
PostPosted: Sat May 22, 2004 9:44 am 
Beginner
Beginner

Joined: Sat Jan 17, 2004 5:35 am
Posts: 25
Location: Birmingham, UK
taz - It could be more stable to work with the CVS version than to attempt to manually merge this feature into 2.1. The roadmap states that a preview release of 3.0 is due for Summer 2004, so it may be worth working with that - at least any bugs that you do spot can be resolved immediately then.

christian - Is the roadmap link in response to my original question, or the off-topic discussion about interceptors? If it is in response to my original question, then specifically how does 3.0 intend to remedy my problem? And if not, can anyone provide an insight into persisting AOP mixins with Hibernate?

Mark


Top
 Profile  
 
 Post subject: cvs vs. merge
PostPosted: Mon May 24, 2004 12:13 pm 
Newbie

Joined: Tue Mar 30, 2004 12:23 pm
Posts: 14
I think Christian's reply was actually in response to my comment about 2.2 vs. Hibernate 3.

I took a quick look at the code over the weekend and it doesn't look like it would be an easy thing at all to merge. Unfortunately, the Hibernate3 timing doesn't work for my project... at least I know that these changes are on the horizon.

Kevin


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 8 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.