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 when 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 currently does. 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 aware of any consequences that such an addition would have, but it seems to me the neatest solution for aspect-based architectures. This problem is going to arise more and more in future with the advent of AOP, and as such it would be great if it could seamlessly integrate with Hibernate. 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