figleaf-developer Mailing List for Figleaf
Status: Alpha
Brought to you by:
steckman
You can subscribe to this list here.
2004 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(78) |
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
---|---|---|---|---|---|---|---|---|---|---|---|---|
2006 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
(1) |
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: Greg S. <ste...@on...> - 2006-06-13 23:12:00
|
Sourceforge rolled out Subversion service and simultaneously made a change to the CVS service (host name) that would require a new CVS checkout. So I took advantage of the opportunity to convert the project to Subversion. The HEAD of CVS was put into Subversion under the path /trunk. CVS is now read-only and can only be accessed as anonymous, not with the developer accounts. General info is here: https://sourceforge.net/svn/?group_id=112919 To get the trunk (where the files are), use the URL https://svn.sourceforge.net/svnroot/figleaf/trunk Greg |
From: <sam...@ma...> - 2004-07-14 14:11:25
|
(note: I sent the original email by accident before I'd finished writing it!) I wrote some simple integration tests for the Mutable/Validation stuff I've been working on (had trouble getting it into CVS, I'll try again later). Anyway, I've created a sample Mutable object Employee, which the following requirements: 1. A name that must be non-null 2. A non-null start date that must be before the end date, if defined 3. An end date that must be after the start date, but can be null 4. A home and mobile number, at least one of which needs to be defined. Must match the pattern XXXX-XXXXXXX 5. A non-null postcode that has to match the pattern XXX XXX 6. An age that must be above 18 and less that 65 So far validating non-null fields works, as does regexp matching for postcode and phone numbers, and integer ranges. Next up I'll add the code to handle object invariant validation - this will catch cross-property changes, such as the end date being after the start date. The EmployeeMutable class looks like this (simple code cut for the sake of brevity): public class EmployeeMutable implements Mutable { public EmployeeMutable() { setupMutableImpl(); } private void setupMutableSupport() { mutableSupport = new MutableImpl(); //setup change handlers for properties... mutableSupport.addChangeHandler(NAME_PROPERTY, new ChangeHandler() { public void change(Object newValue) { name = (String) newValue; } }); mutableSupport.addChangeHandler(START_DATE_PROPERTY, new ChangeHandler() { public void change(Object newValue) { startDate = (Date) newValue; } }); ... //setup validation rules mutableSupport.addValidationRule(NAME_PROPERTY, new NonNullValidationRule()); mutableSupport.addValidationRule(START_DATE_PROPERTY, new NonNullValidationRule()); mutableSupport.addValidationRule(POSTCODE_PROPERTY, new NonNullValidationRule()); mutableSupport.addValidationRule(POSTCODE_PROPERTY, new RegexpValidationRule("[a-zA-Z0-9]{3} [a-zA-Z0-9]{3}")); ... } //delegates to mutableImpl public void commit(ChangeSet set) throws ValidationException { mutableSupport.commit(set); } public boolean isValidChange(ChangeSet set, ValidationCallback callback) { return mutableSupport.isValidChange(set, callback); } } You can now call code like this: ChangeSet set = new ChangeSet(); set.add(new ChangeImpl("age", new Integer(18)); set.add(new ChangeImpl("name", "John Smith")); if (employeeMutable.isValid(set, callback)) { employeeMutable.commit(); } else { showError(callback.getErrorMessage()); } Once I've added invariant support, I'll look at creating automatic implementations so users don't have to, in the same way as I do for ClassDescriptor. Comments? sam |
From: <sam...@ma...> - 2004-07-14 14:04:53
|
i wrote some simple integration tests for the Mutable/Validation stuff I've been working on (had trouble getting it into CVS, I'll try again later). Anyway, I've created a sample Mutable object Employee, which the following requirements: 1. A name that must be non-null 2. A non-null start date that must be before the end date, if defined 3. An end date that must be after the start date, but can be null 4. A home and mobile number, at least one of which needs to be defined. Must match the pattern XXXX-XXXXXXX 5. A non-null postcode that has to match the pattern XXX XXX 6. An age that must be above 18 and less that 65 So far validating non-null fields works, as does regexp matching for postcode and phone numbers, and integer ranges. Next up I'll add the code to handle object invariant validation - this will catch cross-property changes, such as the end date being after the start date. The EmployeeMutable class looks like this (simple code cut for the sake of brevity): public class EmployeeMutable implements Mutable { public EmployeeMutable() { setupMutableImpl(); } //delegates to mutableImpl public void commit(ChangeSet set) throws ValidationException { mutableSupport.commit(set); } public boolean isValidChange(ChangeSet set, ValidationCallback callback) { return mutableSupport.isValidChange(set, callback); } |
From: <sam...@ma...> - 2004-07-13 17:37:48
|
OK, I've created a Mutable interface which describes the following methods: void commit(ChangeSet set) throws ValidationException; boolean isValidChange(ChangeSet set, ValidationCallback callback); I've created a default implementation of this called MutableImpl, which can be used as a helper class by developers. MutableImpl defines: void addChangeHandler(String property, ChangeHandler handler) void addValidationRule(String propertyName, ValidationRule validationRule) ChangeHandler is simple, it just defines: void change(Object newValue); It assumes the value has been validated beforehand. When I've finished the tests for MutableImpl (there are a few cases to finish off, mostly to do with validation) I'll write some examples of use by way of integration tests. I've also added some more validation code and restructured some of the common elements into an abstract class - there will be some more cleanup work here in the future, but at present the rules all work. sam http://www.magpiebrain.com/ |
From: <sam...@ma...> - 2004-07-11 17:52:11
|
The code checked in will now locate an Informative helper class and use that. Lets say you have MyDomainObject in class org.mypackage. The InformativeFactory will now look for MyDomainObjectInformative and use that if found - check the tests in InformativeFactoryImpl for more details. sam |
From: <sam...@ma...> - 2004-07-11 17:48:16
|
Quoting Greg Steckman <ste...@on...>: > sam...@ma... wrote: > > Quoting Greg Steckman <ste...@on...>: > > > > > >>In the Maven project I had a dependency on cglib-asm instead of > >>asm-1.4.3. With asm-1.4.3 all tests pass. > >> > > > > > > Good to know :-) I'm going to start adding more tests to try and improve > test > > coverage (currently less than 50%)... > > > > Oh, I got a clover license for our project, so you might want to look at > > installing one of its plugins for your IDE. The InteliJ integration is > really > > good and it's suprising how useful it is when writing tests. > > > > sam > > http://www.magpiebrain.com/ > > > > ok. There's a maven plugin for it too. Is the jar checked in to CVS? > > Greg > > I'm only using it in my IDE at the moment, so no - I'll get the licence and Jar and check it in... sam http://www.magpiebrain.com/ |
From: <sam...@ma...> - 2004-07-11 17:47:28
|
Quoting Greg Steckman <ste...@on...>: > > This just becomes a ChangeSet specialization - rather than a Change now > being a > > property->new value mapping, we now have a Change property->Array of new > values > > mappings - write still takes an Object, it just happens to be either a > > Collection or an array of values. > > > > Good I didn't think about the ChangeSet scheme since I wasn't working > with that code...I'll keep working with the current scheme and then > convert everything to use the ChangeSet API when it's ready. > > Greg The code is checked it now, but I haven't made any attempt to integrate it yet - I'd appreciate your thoughts on how it works. As it stands at the moment, we have the following: 1. ValidationRule - uses for validating specific properties 2. Validator - takes Property->ValidationRule mappings 3. Change objects - takes Property->Value mappings 4. A ChangeSet - takes a list of Change objects The only thing thats left is the bit of code to actually apply the ChangeSet (think of a ChangeSet as a db transaction, with the Validator as, well, a validation layer). I was thinking about a Mutable interface which defines the following method: boolean commit(ChangeSet set) Validator could then be invoked internally like so: boolean commit(ChangeSet set) { if (validator.isChangeValid(set)) { //apply changeset here } else { //could return false or throw validation exception, invoke callback etc. } } sam http://www.magpiebrain.com/ |
From: Greg S. <ste...@on...> - 2004-07-11 17:43:33
|
sam...@ma... wrote: > Quoting Greg Steckman <ste...@on...>: > > >>In the Maven project I had a dependency on cglib-asm instead of >>asm-1.4.3. With asm-1.4.3 all tests pass. >> > > > Good to know :-) I'm going to start adding more tests to try and improve test > coverage (currently less than 50%)... > > Oh, I got a clover license for our project, so you might want to look at > installing one of its plugins for your IDE. The InteliJ integration is really > good and it's suprising how useful it is when writing tests. > > sam > http://www.magpiebrain.com/ > ok. There's a maven plugin for it too. Is the jar checked in to CVS? Greg |
From: <sam...@ma...> - 2004-07-11 17:39:17
|
Quoting Greg Steckman <ste...@on...>: > In the Maven project I had a dependency on cglib-asm instead of > asm-1.4.3. With asm-1.4.3 all tests pass. > Good to know :-) I'm going to start adding more tests to try and improve test coverage (currently less than 50%)... Oh, I got a clover license for our project, so you might want to look at installing one of its plugins for your IDE. The InteliJ integration is really good and it's suprising how useful it is when writing tests. sam http://www.magpiebrain.com/ |
From: Greg S. <ste...@on...> - 2004-07-11 17:36:10
|
sam...@ma... wrote: > Quoting Greg Steckman <ste...@on...>: > > >>I was working on the drag-and-drop support and thought of another case >>to handle with the validation framework: a property is a collection, and >>an element gets added to the collection. The property setter isn't >>called. Only "get" is needed to get the collection, then the collection >>add method is used. This also applies to the Observable interface - >>intercepting the set call alone won't work. >> >>One solution would be to add "addElement" and "deleteElement" methods to >>the PropertyDescriptor. This gets more complicated when you start to >>consider what if the type is actually a Map, or some unknown custom >>aggregate type, etc. >> >>Greg > > > > This just becomes a ChangeSet specialization - rather than a Change now being a > property->new value mapping, we now have a Change property->Array of new values > mappings - write still takes an Object, it just happens to be either a > Collection or an array of values. > Good I didn't think about the ChangeSet scheme since I wasn't working with that code...I'll keep working with the current scheme and then convert everything to use the ChangeSet API when it's ready. Greg |
From: <sam...@ma...> - 2004-07-11 17:31:35
|
Quoting Greg Steckman <ste...@on...>: > I was working on the drag-and-drop support and thought of another case > to handle with the validation framework: a property is a collection, and > an element gets added to the collection. The property setter isn't > called. Only "get" is needed to get the collection, then the collection > add method is used. This also applies to the Observable interface - > intercepting the set call alone won't work. > > One solution would be to add "addElement" and "deleteElement" methods to > the PropertyDescriptor. This gets more complicated when you start to > consider what if the type is actually a Map, or some unknown custom > aggregate type, etc. > Quoting Greg Steckman <ste...@on...>: > I was working on the drag-and-drop support and thought of another case > to handle with the validation framework: a property is a collection, and > an element gets added to the collection. The property setter isn't > called. Only "get" is needed to get the collection, then the collection > add method is used. This also applies to the Observable interface - > intercepting the set call alone won't work. > > One solution would be to add "addElement" and "deleteElement" methods to > the PropertyDescriptor. This gets more complicated when you start to > consider what if the type is actually a Map, or some unknown custom > aggregate type, etc. > > Greg This just becomes a ChangeSet specialization - rather than a Change now being a property->new value mapping, we now have a Change property->Array of new values mappings - write still takes an Object, it just happens to be either a Collection or an array of values. sam http://www.magpiebrain.com/ |
From: Greg S. <ste...@on...> - 2004-07-11 17:22:43
|
In the Maven project I had a dependency on cglib-asm instead of asm-1.4.3. With asm-1.4.3 all tests pass. Greg |
From: Greg S. <ste...@on...> - 2004-07-11 15:45:11
|
I was working on the drag-and-drop support and thought of another case to handle with the validation framework: a property is a collection, and an element gets added to the collection. The property setter isn't called. Only "get" is needed to get the collection, then the collection add method is used. This also applies to the Observable interface - intercepting the set call alone won't work. One solution would be to add "addElement" and "deleteElement" methods to the PropertyDescriptor. This gets more complicated when you start to consider what if the type is actually a Map, or some unknown custom aggregate type, etc. Greg |
From: Greg S. <ste...@on...> - 2004-07-11 14:57:20
|
sam...@ma... wrote: > I've refactored a few things - ObjectIntrospector has now become a > ClassDescriptorFactory - its job is to: > > 1. Locate ClassDescriptor implementations on the classpath (e.g. > MyObjectClassDescriptor if MyObject passed in) > 2. Create implementations for classes which do not have ClassDescriptor > implementations > > This is tested, references renamed, and is checked in. InformativeFactory now > handles part of what we want - pass in an Informative object, and it gets > returned. Pass in an ClassDescriptor and I create an Observable implementation > and return a composite Informative object. Pass in a POJO and I try and locate > ClassDescriptor and return that as part of a composite object. > > Next up I have to: > > 1. Look for an Informative implementation on the classpath > 2. Handle users defining their own Observable implementations > > We do start hitting some complex cases that need clarification. For example what > if I'm passing in objects of ClassA in where: > > * ClassA doesn't implement ClassDescriptor, Informative or Observable > * ClassAClassDescriptor and ClassAInformative exist on the classpath? > > I think order of precedence should go like this: > > 1. If an object implements Informative, use that > 2. If an Informative implementation exists on the classpath use that > 3. If the object implements ClassDescriptor, use that and implement the rest > 4. If the object has ClassDescriptor on the classpath, use that and implement > the rest > > Actually, I think we need to simplify things (I keep thinking of these things > mid email!) - perhaps we should just support the following: > > 1. If an object passed to the factory implements Informative, use that > 2. If an ibject passed to the factory has an Informative implementation on the > classpath, us that > 3. Otherwise create a implementation. > > Lets not worry about the developer partially implementing Informative - I think > that is getting overly complex! > > sam > Yes, this last thought is the same as the e-mail I just sent. Greg |
From: Greg S. <ste...@on...> - 2004-07-11 14:54:18
|
sam...@ma... wrote: > I think we need to get this sorted properly. With that in mind, I'm going to > write some integration tests for my validation work, check it in and move back > to this - we can pick up the validation/changeset stuff later. The integration > tests can act as a proof of concept so we can come back to it when we have a > better idea as to how a developer will define figleaf applications. > > For the introspector this is my overview of how things stand: > > 1. We have a two interfaces that have to be implemented by an object in order > for it to be useable in figleaf: > -- ClassDescriptor > -- Observable > Which together make Informative > 2. If an object implements Informative, no action is taken > 3. If an object implements one required interface but not the other, then we > look on the classpath for the other, e.g. it implements ClassDescriptor but not > Observable, then we look for Observable > 4. If a required interface cannot be located on the classpath, we create a > default implementation > > All of this is hidden behind the existing InformativeFactory code - the changes > will be completelty transparent to your GUI code. > > Sound ok? > > sam > > > That sounds good. That's what I had in mind. There is one additional case: 3b. If an object implements no required interfaces, then we look on the classpath for it, e.g. we look for Informative. There could be issues of how to handle this if we have n interfaces (instead of just 2). For example, what if the object implements 2 and we need to look for implementations of the other n-2? That's too complicated, so we should require the object to either implement Informative, part of Informative, or none. In the case it implements part of Informative or none, there should be 1 class to look for. For those interfaces not implemented between the two classes, a default implementation is provided. So say there is class MyClass implements interfaceA, interfaceB; Then to make an informative look for "MyClassInformative" which might implement interfaceC, interfaceD (or whatever) to augment it. I think this makes the logic of what to look for simpler and allows a single helper class to implement multiple interfaces that make up Informative. I know at the moment Informative only has 2 superinterfaces, but I think we should leave the flexibility to add more without having to make changes to the introspector. Greg |
From: <sam...@ma...> - 2004-07-11 14:44:41
|
I've refactored a few things - ObjectIntrospector has now become a ClassDescriptorFactory - its job is to: 1. Locate ClassDescriptor implementations on the classpath (e.g. MyObjectClassDescriptor if MyObject passed in) 2. Create implementations for classes which do not have ClassDescriptor implementations This is tested, references renamed, and is checked in. InformativeFactory now handles part of what we want - pass in an Informative object, and it gets returned. Pass in an ClassDescriptor and I create an Observable implementation and return a composite Informative object. Pass in a POJO and I try and locate ClassDescriptor and return that as part of a composite object. Next up I have to: 1. Look for an Informative implementation on the classpath 2. Handle users defining their own Observable implementations We do start hitting some complex cases that need clarification. For example what if I'm passing in objects of ClassA in where: * ClassA doesn't implement ClassDescriptor, Informative or Observable * ClassAClassDescriptor and ClassAInformative exist on the classpath? I think order of precedence should go like this: 1. If an object implements Informative, use that 2. If an Informative implementation exists on the classpath use that 3. If the object implements ClassDescriptor, use that and implement the rest 4. If the object has ClassDescriptor on the classpath, use that and implement the rest Actually, I think we need to simplify things (I keep thinking of these things mid email!) - perhaps we should just support the following: 1. If an object passed to the factory implements Informative, use that 2. If an ibject passed to the factory has an Informative implementation on the classpath, us that 3. Otherwise create a implementation. Lets not worry about the developer partially implementing Informative - I think that is getting overly complex! sam |
From: <sam...@ma...> - 2004-07-11 13:12:07
|
I think we need to get this sorted properly. With that in mind, I'm going to write some integration tests for my validation work, check it in and move back to this - we can pick up the validation/changeset stuff later. The integration tests can act as a proof of concept so we can come back to it when we have a better idea as to how a developer will define figleaf applications. For the introspector this is my overview of how things stand: 1. We have a two interfaces that have to be implemented by an object in order for it to be useable in figleaf: -- ClassDescriptor -- Observable Which together make Informative 2. If an object implements Informative, no action is taken 3. If an object implements one required interface but not the other, then we look on the classpath for the other, e.g. it implements ClassDescriptor but not Observable, then we look for Observable 4. If a required interface cannot be located on the classpath, we create a default implementation All of this is hidden behind the existing InformativeFactory code - the changes will be completelty transparent to your GUI code. Sound ok? sam |
From: <sam...@ma...> - 2004-07-11 10:14:47
|
Quoting Greg Steckman <ste...@on...>: > Hi Sam, > > I was having a look at your introspector code again. We might rethink > how to handle multiple interfaces. For instance what if I have a class > that implements all of Informative, but it is a helper class not the > "real" class? Perhaps we need to look for a class called > o.getClass().getName()+"Informative" and it must implement Informative. Which i do support now for ClassDescriptors - if you pass MyClass into figleaf, the introspector looks for a class MyClassClassDescriptor on the classpath and uses that as the Informative implementation. Ideally we could expand this - have MyClassObservable, MyClassClassDescriptor etc, and allow figleaf to handle some aspects of this automatically. > Also this helper class will probably need to get a reference to the > underlyingObject at some point in order to do its work correctly. We > might want to impose that these helper classes have a single parameter > constructor that takes the underlying object as argument. Yep - its an easy matter if I find the helper class (as detailed above) to locate the correct constructor to instantiate. We could even take the same appraoch with something like a PropertyDescriptor if we wanted to make them instance specific. > In InformativeImpl you have implemented change listeners, but it looks > like only if the caller invokes setProperty(String propertyName, Object > value). But that method isn't in any interfaces, so I don't think it'll > ever get called. I think the listener functionality needs to be placed > in the proxy implementation, in the InformativeCallbacks method. Erm, these methods *should* get called (InformativeImplTest) - they should be implementing the methods defined in Observable. I don't have any tests for InformativeImpl, I'll add some. > If it's ok with you, I can make these changes and reduce some of the > redundancy between the interfaces I made and the ones you made. I will improve support for locating the helper classes - I can refactor out some helper classes for the job so it should be an easy job. I'm not sure InformativeImpl needs to be changed as its listener methods should be working fine. I do need to allow the users implementation of Observable to be called - at present my implementation is always used... I have made one small change to your code for thr purposes of my validation implementation - I changed the PropertyDescriptor version on my local code to not return Method objects. It still takes the read and write methods in to an object, it just abstracts out the calling of these methods. Changing your view code actually involved removing rather than adding code. I also added an AbstractPropertyDescriptor class to allow easy definition by the developer, so now they can do: PropertyDescriptor nameProperty = new AbstractPropertyDescriptor(String.class, "Name", "The things Name") { public Object read(Object target) { return getName(); } public void write(Object target, Object value) { setName((String)value); } }; > > P.S. I still only get some of the mails sent to the list, so it's best > to CC me for now. Will do sam http://www.magpiebrain.com/ |
From: Greg S. <ste...@on...> - 2004-07-10 19:13:56
|
Regarding #2. There will have to be some mechanisms added so that the object can specify to the gui more details, such as when to use a chooser. I'll leave that for another day. For now drag-and-drop everywhere should get us going. Greg Quoting Greg Steckman <steckman@on...>: > Any ideas on how to establish object associations/relationships other > than using drag and drop? > > > Greg From a UI point of view, I can see three approaches which a (rich/thick client) UI might want to use: 1. Drag and drop ala the existing NO UI. When done right can be very easy and intuitive to use. The only problems here are handling it consistently - the moment the user realises they can drag and drop one thing, they start trying to do it with everything. 2. Drop-down combos/list choosers. Drop down combos work with a *small* number of options (< 10 I"d say), but in some circumstances might work better than drag and drop. 3. Popup dialogs - the user clicks on an button next to the field in question and they then get a chooser dialog (could be a standrad built in dialog used for queries). 4. Automatic associations - in some cases the system might define associations porgramatically. Given that we are building this from the groud up, providing consistent drag and drop should be an easy task and works in most circumstances, and I would prefer this approach, accepting that the user could drop in their own views (or alternates we provide) which could support other association mechanisms - I"m not sure what you think on the matter? |
From: Greg S. <ste...@on...> - 2004-07-10 19:05:30
|
Hi Sam, I was having a look at your introspector code again. We might rethink how to handle multiple interfaces. For instance what if I have a class that implements all of Informative, but it is a helper class not the "real" class? Perhaps we need to look for a class called o.getClass().getName()+"Informative" and it must implement Informative. Also this helper class will probably need to get a reference to the underlyingObject at some point in order to do its work correctly. We might want to impose that these helper classes have a single parameter constructor that takes the underlying object as argument. In InformativeImpl you have implemented change listeners, but it looks like only if the caller invokes setProperty(String propertyName, Object value). But that method isn't in any interfaces, so I don't think it'll ever get called. I think the listener functionality needs to be placed in the proxy implementation, in the InformativeCallbacks method. If it's ok with you, I can make these changes and reduce some of the redundancy between the interfaces I made and the ones you made. Greg P.S. I still only get some of the mails sent to the list, so it's best to CC me for now. |
From: <sam...@ma...> - 2004-07-09 08:23:08
|
Quoting Greg Steckman <ste...@on...>: > Any ideas on how to establish object associations/relationships other > than using drag and drop? > > > Greg From a UI point of view, I can see three approaches which a (rich/thick client) UI might want to use: 1. Drag and drop ala the existing NO UI. When done right can be very easy and intuitive to use. The only problems here are handling it consistently - the moment the user realises they can drag and drop one thing, they start trying to do it with everything. 2. Drop-down combos/list choosers. Drop down combos work with a *small* number of options (< 10 I'd say), but in some circumstances might work better than drag and drop. 3. Popup dialogs - the user clicks on an button next to the field in question and they then get a chooser dialog (could be a standrad built in dialog used for queries). 4. Automatic associations - in some cases the system might define associations porgramatically. Given that we are building this from the groud up, providing consistent drag and drop should be an easy task and works in most circumstances, and I would prefer this approach, accepting that the user could drop in their own views (or alternates we provide) which could support other association mechanisms - I'm not sure what you think on the matter? sam http://www.magpiebrain.com/ |
From: Greg S. <ste...@on...> - 2004-07-09 04:14:01
|
For some reason I'm not getting list e-mails in a timely manner...I had to go to Sourceforge to read them! At any rate, the last you mention on this topic was: "If we edit an object using a changeSet which transparently handles validation, an object will never become invalid. It also lets us handle validation of a group of properties, and still supports the user just defining simple validation rules if required. If this is the only way for figleaf to change properties, we also do not need to worry about proxying the properties. A ChangeSet can also be easily sent over the wire if required. My example using ChangeSets is coming on nicely - hopefully once I can show it working it will better show the advantages of the approach." I think this is an interesting approach to the problem. There's one question about desired behavior that we need to consider. Do we want Figleaf to augment the behavior of POJO's in such a way that if Class A calls a setter on Class B, Class B will guard itself or prevent itself from being saved because of validation logic added by the Figleaf augmentation process? Considering a concrete example, say ClassB has a property "void setMyInteger(int x)". Now with Figleaf the developer causes it to get augmented such that it is invalid for the property "MyInteger" to be negative. The GUI will now prevent negative values from being entered on its form by using whatever validation system we come up with. But if an instance of ClassB is accessed by ClassA, which knows nothing of Figleaf, do we still want ClassB to be unsavable/unsettable with a negative value? If the answer is yes, then it would seem there is no alternative than to intercept all calls to the methods that are identified as properties (with a PropertyDescriptor). But it also raises interesting questions such as what should be done if an invalid property is attempted to be set? Ignore it, throw an exception? That leads me to another thought. What if I want to program my classes explicitly with validation logic? I want the Figleaf GUI to also be able to use it as well as the other "semi POJO's" in my domain model. Yes it makes my class dependent on Figleaf API's, but if I don't use the Figleaf API then I'd have to write my own, which is more work. The point would be to force Class A to know that ClassB.setMyInteger has a limit of acceptable values and might throw an exception (or do something else) if it is attempted to set an invalid value. Greg |
From: Greg S. <ste...@on...> - 2004-07-09 01:36:05
|
Any ideas on how to establish object associations/relationships other than using drag and drop? Greg |
From: <sam...@ma...> - 2004-07-08 12:45:10
|
> I think we need validation at two levels. First the individual > properties, and second on the whole object. Agreed (see later). <snip> > >You are now poluting property accesing methods with figleaf dependant code. > >Developers now need to explicitly invoke validation for all their set > methods, > >and have to do so in a way that ties their object to figleaf. I prefer to > deal > >with validation at the point we describe the properties (or general > 'action' > >methods) to the figleaf system, that is in the PropertyDescriptor. > > > Polluting was intentional on my part, as I envision this as one primary > way to write classes for the system if you don't care about portability. > It is potentially the most straightforward and powerful way to write > behaviorially complete objects: but they get contaminated with Figleaf > APIs. My approach (outlined below) moves the requirement for validation into methods defined in Informative, rather than in the domain object's property methods - the coupling is then only present in those areas that get invoked directly by figleaf itself. > >>Case B: The implementation you suggest: > >> > >>public class MyInformative implements Informative{ > >> public void setPropertyA(Object value){...} > >> public Object getPropertyA(){...} > >> > >> PropertyDescriptor[] getPropertyDesctiptors(){ > >> PropertyDescriptor[] pds=new PropertyDescriptor[1]; > >> pds[0]=new MyInformativePropertyDescriptor( > >> > >>getMethod("getPropertyA", null). > >> > >>getMethod("setPropertyA", new Class[]{Object.class}), > >> > >>"Property A", > >> > >>"Description of Property A"); > >> return pds; > >> } <snip> > > > >I wouldn't call methods like that - I'd call them directly on the object to > gain > >compile-time checking and simpler looking code. > > > How would you implement a PropertyDescriptor to call the method directly > without needing one PropertyDescriptor class for each individual property? Well, either you do have one PropertyDescriptor per-object instance per-property (which isn't a huge overhead), or you keep the PropertyDescriptor as being per-class and have: Object read(Object objectToReadFrom) void write(Object objectToWriteTo, Object newVal) > >>In the first case I used an object that's defined elsewhere so that > >>makes the code look shorter but in reality it probably won't be. I > >>prefer the listener approach of Case A because I think it has more > >>flexibility and is a standard pattern so users will understand the API > >>right away. > >But users have to define validation logic and invoke it themselves - my > approach > >just has them define validation rules, and everything else is handled for > them, > >making thw system simpler for them to use. > > > I was thinking that we could proxy the property methods so the > validation logic could be implemented elsewhere. I think the same > results can be had with either approach. But your example has the user defining validation in the getters/setters they write. We might choose to create proxies and insert validation code there (althought I don't think we need to - again see below), although I accept I may of misunderstood your ealier example. > >You then require that figleaf has to proxy method calls. You also expose > >implementation detail. The UI then has to invoke the method itself, catch > the > >exceptions etc. What about remoting? What if invoking the property actually > >invokes a webservices call? Now we have to create a fake Method which we > can't > >easily do (Method is a final class). > Remoting is a whole can of worms. Any method should be taggable as > "remote", which means it is executed on the server object, not on the > remote side. Again it requires proxies. I don't see a way around that in > any case. One advantage with using a Method, Figleaf itself can alter > the method returned by the PropertyDescriptor to point to a Figleaf > implemented interceptor. However that is not an argument for exposing Method objects directly from a PropertyDescriptor. > >>I also prefer using the Methods because I > >>think they are more flexible. They can reference any method in any class > >>without having to implement an extra class just for redirection. > > > >I don't agree really - where would you need an object to expose the method > of > >another class? > > > If PropertyDescriptor is a separate class, then the Method it refers to > is in a different object. I accept that, but my approach still handles that and doesn't expose the method > > >>If a class doesn't implement Informative itself, then everything needs > >>to be handled by the Proxy in either case. > > > >Which I already do - exposing the method is no simpler than directly > invoking it > >inside the property, I've had proxies working in both ways. > > > > That's why I think we might be splitting hairs. Probably everything can > be done either way. It's not even clear to me one implementation is > better than the other at this point. For me what it comes down to is this - I cannot see and advantage to exposing a Method object from the PropertyDescriptor class (be it a class or instance specific object), and can in fact see several disadvantages to it. I'd rather resolve this now rather than later, as the longer we leave it the more work is involved in changing it. > > >In reality validation at a property-by-property basis is simple from > developer > >terms, but is often not good enough, for example what if a customer bean has > 3 > >telephone number fields, and one of them has to be non-null? Neither of our > >approaches work, as we are still validating on a property-by-property basis > - > >even a final-mile validation check on the object as a whole happens to late, > as > >by then the object might be in an invalid state. > > > If the object is in an invalid state that is OK. The important thing is > to not persist data that is invalid. If the object is deemed to be > invalid, it can be reloaded. > > >I say lets rethink the problem. We need to not only validate a single > property > >change, but the validaity of a set of changes on an object. Lets add the > >following method to Informative: > > > >boolean commitChanges(ChangeSet set) > > > >Lets keep your add and remove validation listeners on Informative. A > ChangeSet > >is a set of changes on an object, and now becomes the only mechanism by > which > >figleaf writes changes to an object - it consists of a series of property > change > >messages. I'll knock up a prototype in the next day or so and see if its a > >workable idea. > > > This gets back to whether the object is persisted after each property > set call, or only after a "save event" occurs (clicking a save button, > for example). The latter is a simpler case to handle, because you can > call all the property setters, and then before you want to save the > object call a single validate() method. The object then validates > itself. A proxy can intercept the call to validate() and redirect it to > another object that has the validation logic if required. > > I don't see a problem with the object transiently becoming invalid. As > long as it's not saved or used for anything while it's invalid. It is > essentially a data holder for input forms. If we edit an object using a changeSet which transparently handles validation, an object will never become invalid. It also lets us handle validation of a group of properties, and still supports the user just defining simple validation rules if required. If this is the only way for figleaf to change properties, we also do not need to worry about proxying the properties. A ChangeSet can also be easily sent over the wire if required. My example using ChangeSets is coming on nicely - hopefully once I can show it working it will better show the advantages of the approach. sam http://www.magpiebrain.com/ |
From: Greg S. <ste...@on...> - 2004-07-07 16:10:17
|
We might be splitting hairs on this topic. I think we can express a couple of rules that the system needs to follow: 1. A single object instance implements all behavior required of that object. 2. A single class implementation can implement all behavior required of that class. 3. Optionally, multiple classes can be defined which when taken together implement all required behavior. Rule 2 allows you to choose to implement all behavior in one class, and that class will be dependent on Figleaf. The approach of using proxies is to be able to factor out Figleaf dependent code into helper classes (rule 3), and have dynamic proxies created to make a class that looks like it is behaviorially complete, but really under the covers there are several classes doing the work. sam...@ma... wrote: >>Let's consider an implementation of a class that implements the >>Informative interface itself. >> >>Case A: Current implementation + modify PropertyDescriptor to add the >>methods: >>void addValidationListener(ValidationListener listener); >>void removeValidationListener(ValidationListener listener); >> >> > >What would listen to validation events? The GUI - in fact parts of the GUI would >only need to listen to specific properties. As such I think a more fine-grained >validation listener approach would benifit us - allow validation callbacks on >specific property calls. By passing a callback into a validation call (or even >as a result of calling get) the Informative object doesn't need to manage >multiple ValidationListeners, and the GUI doesn't have to worry about detaching >listeners > > I think we need validation at two levels. First the individual properties, and second on the whole object. >>public class MyInformative implements Informative{ >> PropertyDescriptor[] pds; >> Object propA; >> >> public void setPropertyA(Object value){ >> boolean invalid=true; >> >> //do some validation logic >> //set invalid as required >> >> > > > >> if(invalid){ >> pds[0].fireValidationInvalid("reason string goes here"); >> }else{ >> propA=value; >> pds[0].fireValidationValid(); >> } >> } >> >> > > >You are now poluting property accesing methods with figleaf dependant code. >Developers now need to explicitly invoke validation for all their set methods, >and have to do so in a way that ties their object to figleaf. I prefer to deal >with validation at the point we describe the properties (or general 'action' >methods) to the figleaf system, that is in the PropertyDescriptor. > > Polluting was intentional on my part, as I envision this as one primary way to write classes for the system if you don't care about portability. It is potentially the most straightforward and powerful way to write behaviorially complete objects: but they get contaminated with Figleaf APIs. >>Case B: The implementation you suggest: >> >>public class MyInformative implements Informative{ >> public void setPropertyA(Object value){...} >> public Object getPropertyA(){...} >> >> PropertyDescriptor[] getPropertyDesctiptors(){ >> PropertyDescriptor[] pds=new PropertyDescriptor[1]; >> pds[0]=new MyInformativePropertyDescriptor( >> >>getMethod("getPropertyA", null). >> >>getMethod("setPropertyA", new Class[]{Object.class}), >> >>"Property A", >> >>"Description of Property A"); >> return pds; >> } >> >> class MyInformativePropertyDescriptor implements PropertyDescriptor{ >> private Method readMethod, writeMethod; >> private String name, description; >> >> MyInformativePropertyDescriptor(Method readMethod, Method >>writeMethod, String name, String desc){ >> this.readMethod=readMethod; >> this.writeMethod=writeMethod; >> this.name=name; >> this.description=desc; >> } >> >> public Object read(){ >> return readMethod.invoke(this.MyInformative, null); >> } >> >> public void write(Object o, ValidationCallback callback){ >> //do validation >> >> if(valid){ >> writeMethod.invoke(this.MyInformative, new Object[]{o}); >> } >> } >> } >>} >> >> > >I wouldn't call methods like that - I'd call them directly on the object to gain >compile-time checking and simpler looking code. > How would you implement a PropertyDescriptor to call the method directly without needing one PropertyDescriptor class for each individual property? >>In the first case I used an object that's defined elsewhere so that >>makes the code look shorter but in reality it probably won't be. I >>prefer the listener approach of Case A because I think it has more >>flexibility and is a standard pattern so users will understand the API >>right away. >> >> > >But users have to define validation logic and invoke it themselves - my approach >just has them define validation rules, and everything else is handled for them, >making thw system simpler for them to use. > > I was thinking that we could proxy the property methods so the validation logic could be implemented elsewhere. I think the same results can be had with either approach. >>If we do that, I don't see the benefit of not using Methods >>in the PropertyDescriptor. >> >> > >You then require that figleaf has to proxy method calls. You also expose >implementation detail. The UI then has to invoke the method itself, catch the >exceptions etc. What about remoting? What if invoking the property actually >invokes a webservices call? Now we have to create a fake Method which we can't >easily do (Method is a final class). > > Remoting is a whole can of worms. Any method should be taggable as "remote", which means it is executed on the server object, not on the remote side. Again it requires proxies. I don't see a way around that in any case. One advantage with using a Method, Figleaf itself can alter the method returned by the PropertyDescriptor to point to a Figleaf implemented interceptor. >>I also prefer using the Methods because I >>think they are more flexible. They can reference any method in any class >>without having to implement an extra class just for redirection. >> >> > >I don't agree really - where would you need an object to expose the method of >another class? > If PropertyDescriptor is a separate class, then the Method it refers to is in a different object. >>If a class doesn't implement Informative itself, then everything needs >>to be handled by the Proxy in either case. >> >>Greg >> >> > >Which I already do - exposing the method is no simpler than directly invoking it >inside the property, I've had proxies working in both ways. > That's why I think we might be splitting hairs. Probably everything can be done either way. It's not even clear to me one implementation is better than the other at this point. >In reality validation at a property-by-property basis is simple from developer >terms, but is often not good enough, for example what if a customer bean has 3 >telephone number fields, and one of them has to be non-null? Neither of our >approaches work, as we are still validating on a property-by-property basis - >even a final-mile validation check on the object as a whole happens to late, as >by then the object might be in an invalid state. > If the object is in an invalid state that is OK. The important thing is to not persist data that is invalid. If the object is deemed to be invalid, it can be reloaded. >I say lets rethink the problem. We need to not only validate a single property >change, but the validaity of a set of changes on an object. Lets add the >following method to Informative: > >boolean commitChanges(ChangeSet set) > >Lets keep your add and remove validation listeners on Informative. A ChangeSet >is a set of changes on an object, and now becomes the only mechanism by which >figleaf writes changes to an object - it consists of a series of property change >messages. I'll knock up a prototype in the next day or so and see if its a >workable idea. > >sam >http://www.magpiebrain.com/ > > > This gets back to whether the object is persisted after each property set call, or only after a "save event" occurs (clicking a save button, for example). The latter is a simpler case to handle, because you can call all the property setters, and then before you want to save the object call a single validate() method. The object then validates itself. A proxy can intercept the call to validate() and redirect it to another object that has the validation logic if required. I don't see a problem with the object transiently becoming invalid. As long as it's not saved or used for anything while it's invalid. It is essentially a data holder for input forms. Greg |