Vincent
What does an equals method for a component defined with a BasicComponentDescriptor?
Pierre
Vincent
What does an equals method for a component defined with a BasicComponentDescriptor?
Pierre
Hi Pierre,
Components handled by Jspresso J2SE proxies rely on equals/hashCode of java.lang.Object (see BasicComponentInvocationHandler.computeEquals/computeHashCode). Have you faced any problem regarding this implementation ? In fact, I don't see any other option...
Regards,
Vincent
Vincent
rely on equals/hashCode of java.lang.Object (see BasicComponentInvocationHandler.computeEquals/computeHashCode)
It's what I suspected.
Have you faced any problem regarding this implementation ?
I'm trying to implement component with special cases (see code below). One of them is not a conversion of a null value. Then I need to check the value of the attributes, not the reference of the instance.
I could do the checking in the interceptor class but I'd like to avoid it. Then the use of equals.
IMH, the checking of reference for component is not a good idea: a component has no identity, it's a value object. If each attribute of two components has same value, then the two components are equals.
In fact, I don't see any other option...
If there is no way to do such a thing from inside the code of computeEquals, what about adding in a service delegate class the methodes
public boolean equals(IComponent c, Object other)
public int hashCode(IComponent c)
Regards
Pierre
public interface EventTypeService {
static final EventType VOID = new NullEventType();
static final EventType PAYSLIP = new PayslipEventType();
final class NullEventType
extends DefaultComponent
implements EventType
{
@Override public String getName() {return EMPTY_STRING;}
@Override public void setName(String name) {}
}
final class PayslipEventType
extends DefaultComponent
implements EventType
{
@Override public String getName() {return "payslip";}
@Override public void setName(String name) {}
}
}
public class EventTypeInterceptor
extends EmptyLifecycleInterceptor<EventType> {
@Override
public void onLoad(EventType type) {
if (null == type) {
type = EventType.VOID;
}
else if (EventType.PAYSLIP.equals(type)) {
type = EventType.PAYSLIP;
}
}
@Override
public boolean onPersist(EventType type, IEntityFactory entityFactory,
UserPrincipal principal, IEntityLifecycleHandler entityLifecycleHandler)
{
return toPersistence(type);
}
@Override
public boolean onUpdate(EventType type, IEntityFactory entityFactory,
UserPrincipal principal, IEntityLifecycleHandler entityLifecycleHandler)
{
return toPersistence(type);
}
private boolean toPersistence(EventType type) {
if(type == EventType.VOID) {
type = null;
}
return false;
}
}
Pierre,
I think I get the general idea (using constants for component values), but I think I don't understand the EventTypeInterceptor code (i.e. you change the reference of the parameter passed-in). Well, if I translate correctly, this lifecycle interceptor should be registered on the component-owning class (the one that holds the type reference), right ?
IMH, the checking of reference for component is not a good idea: a component has no identity, it's a value object. If each attribute of two components has same value, then the two components are equals.
I agree with you. But as far as I can remember, I had some problems with Hibernate and Jspresso dirty state detection (but I may be wrong, so let's have a look at it again).
Equality and hashCode can be implemented based on component properties equality (hashCode). Everything is actually accessible from the proxy implementation class, so no technical problem on this side.
Do you know that you can even do it yourself, i.e. change the default implementation used by Jspresso for implementing the components J2SE proxy without modifying the Jspresso code itself ? It may be a good scenario to try it out :
Now, each and every component created in your Jspresso app should use your implementation. Same is also available for entities.
Meanwhile, I'm taking a look to the default behaviour.
Best,
Vincent
Pierre,
While trying to implement it, I've come up to the fact that the original proxy also have to be passed to the computeHashCode method (as it is done for the computeEquals method) so that you can leverage the straightGetProperties method. So you can't do it as of now.
Pierre,
I've implemented the default behaviour as you suggested. The dirty checking problem was linked to something else, so everything runs fine.
I've published a new SNAPSHOT including the fix as well as the computeHashCode API change to align it with computeEquals.
Tell me if it fits.
Best,
Vincent
Vincent,
I don't understand the EventTypeInterceptor code (i.e. you change the reference of the parameter passed-in). Well, if I translate correctly, this lifecycle interceptor should be registered on the component-owning class (the one that holds the type reference), right ?
I haven't tested this code yet. So may be I'm wrong. The idea is to replace any special case value of EventType by a value acceptable by the persistence layer.
I'd like to do it in a unique place.
In the current code, i did it at the level of each attribute of type EventType, e.g. Event.eventType with a interceptor class EventInterceptor.
Regards
Pierre
I haven't tested this code yet. So may be I'm wrong. The idea is to replace any special case value of EventType by a value acceptable by the persistence layer.
I fear that your interceptor won't work. Lifecycle interceptors are designed to change the state of a saved component / entity not its actual reference. Your interceptor doesn't hurt but does nothing (i.e. you only assign parameter another reference, which just doesn't have any effect in java).
I'd like to do it in a unique place.
I don't think it's possible that way. We would need something like readResolve / writeReplace that is used in java serialization but applied to the persistence operation. IMHO this would open the door to a lot of problems, particularly regarding the binding.
BTW, why don't you want to use an interceptor on the EventType.name property that takes care of substituting the null and "payslip" strings by final constants (use the IPropertyProcessor.interceptSetter) ? Then checking for equality would make it.
HTH,
Vincent
I've published a new SNAPSHOT
Thanks. I'm downloading it.
I don't think it's possible that way.
OK, I think I'll stay with the current code (i.e. interceptor with each attribute using such a type)
to use an interceptor on the EventType.name property that takes care of substituting the null and "payslip" strings by final constants (use the IPropertyProcessor.interceptSetter)
I use it in the current code, to manage special cases in an component attribute between the model and the UI:
- the interceptSetter returns a special case value to the model from the value returned by the UI, e.g. null =>SomeComponent.VOID
- and a modified valueConnector gets a value for the UI from the special case value of the model, e.g. SomeComponent.VOID => null
But:
- it doesn't manage a component attribute with a type using special case values when the component is created or loaded from the persistence layer
- the last job is quite painful to write. Would it be possible to do the job with some kind of getter in IPropertyProcessor?
Regards
Pierre
Pierre,
it doesn't manage a component attribute with a type using special case values when the component is created or loaded from the persistence layer
Today, lifecycle interceptors are only triggered for entities. Following your idea, it would make sense that inlined components also get their lifecycle interceptors triggered, right ? So for instance, when an entity is updated to the persistence store, its interceptors get triggered and the interceptors of its inlined components also ? It definitely makes sense.
Would you open a feature request ?
Oups, it's already implemented. So what you say is that onCreate and onLoad don't get triggered for inline components (following their owning entity lifecycle) ?
and a modified valueConnector gets a value for the UI from the special case value of the model, e.g. SomeComponent.VOID => null
If I understand right, and if I take back your example of null <-> SomeComponent.VOID, you want :
This seems to me really complicated, but maybe I'm missing the use case behind, and the advantage of such a construct.
BTW, why wouldn't you just declare a (writable but stateless) computed property that takes care of this maping ? e.g. all the internal framework mecanisms (binding, persistence, ...) still use the original property (with null value) and you code your business rules against the computed property (that deals with SomeComponent.VOID and takes care of the mapping between the constants and the actual original values) ? IMHO, it would bring all the benefits you need, i.e. the mapping is written once and kept in one place, is only used for accessors on the component and you don't have to play with Jspresso binding implementation.
Would it be possible to do the job with some kind of getter in IPropertyProcessor
The actual implementation doesn't use plain getters for Hibernate to retrieve the state of the entity/component to persist. So it wouldn't be useful. More importantly, I don't feel like opening the door to linking some triggered behaviour to reading a property.
HTH,
Vincent
Vincent,
Oups, it's already implemented. So what you say is that onCreate and onLoad don't get triggered for inline components (following their owning entity lifecycle) ?
As I understand things:
- a LifeCycleInterceptor can be used to add some custom actions between the model and the persistence layer;
- and a PropertyProcessor can be used to do it between the model and the UI, but only in the direction UI->model
For what I have already tested, this works with components and entities.
If I understand right,
You do, indeed! Sorry for my bad explanation, thought in progess...
yes: if a value has never been initialize, I don't want to show any "default" value
not always, i.e.: for string it doesn't matter but yes for numeric component like Money ones.
The idea is to keep the state "not still initialized" in the persistence layer.
This seems to me really complicated
Yes, I agree. But ATM it's the only way I have thought of:
- to avoid any null value in the model (Null Object pattern)
- to keep the same behavior in the UI (null == nothing in a field)
BTW, why wouldn't you just declare[...]
Mmmm, good. I'll try it.
Regards
Pierre
Pierre,
Just for the sake of accuracy
:
a LifeCycleInterceptor can be used to add some custom actions between the model and the persistence layer
Right (even if onCreate is not directly linked to the persistence layer, i.e. in-memory instanciation).
and a PropertyProcessor can be used to do it between the model and the UI, but only in the direction UI->model
Not only between UI -> model (even if this is the most common case). Property processors will be triggred every time a modifier on the property is called (so even during a business rule, that is UI-less).
keep us informed about your tries.
Best,
Vincet