RF Editors dealing with null proxies/subeditors

230 views
Skip to first unread message

Arash

unread,
Aug 11, 2012, 6:26:31 AM8/11/12
to google-we...@googlegroups.com
Hi,

There seem to be similar issues posted previously but none of them explains this seem to be very trivial scenario. I have the typical Person/Address relationship in my entities. The person's address can be null but if there is an address, the city and zip can not be null (please take a look at the entities below). I utilize RequestFactoryEditorDriver to display/edit the person and address utilizing PersonEditor and AddressEditor respectively.  I have to mention editing an existing Person with an Address works just fine so the editors are working and implemented somewhat properly.
Creating a person get done by:  request.persist().using(person). If I don't provide an address before starting the driver(setValue is called with null), the PersonEditor does its job but AddressEditor's value gets ignored which is expected but not desired. If I create an address (via the same requestContext) and assign it to the person (as commented out) before creating my persist context and starting the driver, then I "have to" have an address. If I leave the city/zip empty, the editors validation detects the error and demands the fields to be set. But what if I just want to ignore the address for some persons and provide one for others.
Basically I want to choose creating an addressProxy and attaching it to the context. If the AddressEditor is empty then there is no need to create an AddressProxy, but if there is a value in the AddressEditor then the validation and flush need to work as expected. 
/*
 AddressProxy address = request.create(AddressProxy.class);
 proxy.setAddress(address);
*/
 request.persist().using(person);
 
here's the jest of AddressEditor
public class AddressEditor extends Composite implements HasRequestContext<AddressProxy>,
ValueAwareEditor<AddressProxy>, LeafValueEditor<AddressProxy>,
HasEditorErrors<AddressProxy> {
....
}
I tried the OptionalFieldEditor in AddressEditor as follow but I wasn't sure if I should utilize it in PersonEditor or AddressEditor

  interface Driver
   extends
   RequestFactoryEditorDriver<AddressProxy, OptionalFieldEditor<AddressProxy, AddressEditor>> {
    }

public class Person{
    @NotNull
    @Size(min = 3, max = 30)
    @Column(name = "first_name")
    private String firstName;

    @NotNull
    @Size(min = 3, max = 30)
    @Column(name = "last_name")
    private String lastName;

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "address_id", unique = true)
    private Address address;
}

public class Address{
    @Size(min = 3, max = 64, message = "Street lenght needs to be between 3 to 64 chars")
    @Column(name = "street")
    private String street;

    @NotNull
    @Size(min = 3, max = 30)
    @Column(name = "city")
    private String city;

    @NotNull
    @Size(min = 5, max = 10)
    @Column(name = "zip")
    private String zip;
}
As you can tell, I have exhausted my options by implementing every possible editor interfaces and testing different scenarios. 
Any help would be greatly appreciated. 
Arash

Thomas Broyer

unread,
Aug 23, 2012, 4:37:52 AM8/23/12
to google-we...@googlegroups.com
Basically, you want to always have an AddressProxy instance, so first start by ensuring the edited value is never 'null'.
Next, in case the user empties the fields, you don't want to send an empty AddressProxy to the server as it'll fail validation, even if it's not "attached" to the PersonProxy.

For now, RF will send all created/edited objects to the server, regardless of whether they're actually "used" (passed as arguments to service methods or referenced by other proxies). I have a TODO to fix this (and reduce the payload size) but there was a a "feature" of RF where you could edit/create proxies and send them to the server without any service method call, just to validate them and be a no-op if they're valid, so it's not as easy as it sounds. In the mean time, that means you have to make sure the AddressProxy is always valid, particularly if not used.

This all boils down then to create an AddressProxy in all cases, but make sure it's not empty before fire()ing the RequestContext.

I can think of two ways to always have an AddressProxy: create one in the RequestContext, or create an object that implements AddressProxy and replace it with a "real" proxy just before fire()ing iff the "fake" proxy is not empty.

And I can think of 2 places to do that:
  • Create the proxy and assign it (if needed) before edit()ing the PersonProxy, and then "validate" it and "fix" it (reset the address of the person to 'null' iff empty, and if the AddressProxy is a "real" proxy then put dummy data so it'll validate on the server-side; the dummy data won't be used as the Address is no longer attached to the Person) after flush()ing the EditorDriver but before fire()ing the RequestContext.
  • Extend OptionalFieldEditor and override getValue and setValue. In setValue, if the value is null, create the AddressProxy and pass it to super.setValue; in getValue, if the address is empty, return null (and if it's a "real" proxy, put dummy data into it)
Much simpler though, you could consider an empty Address as the equivalent of "no address at all" in your validation logic.
Well, simpler on the client side, probably not on the server side…
One possibility is to avoid validating the Address at the RequestFactoryServlet level (create it with a ServiceLayerDecorator that implements the validate() method to validate only some specific validation groups, or special-case the Address object) and instead either validate it as part of validating the Person (using a custom validator on the 'address' field) or validate it as part of the saving process (explicitly validate it, using some specific validation groups for instance, in your persist() service method).

Arash

unread,
Aug 25, 2012, 5:57:16 PM8/25/12
to google-we...@googlegroups.com
Thank you Thomas for your elaborate response. I had to implement your solutions to fully grasp the idea. I summarize the result here for anyone who may be interested:
OptionalFieldEditor is an elegant solution to dynamically create non existing proxies (Thomas's second solution). It takes care of attaching the newly created address proxy to the personProxy (automatically) so you don't have to actually make the assignment. This is what you need:

public class OptionalAddressEditor extends OptionalFieldEditor<AddressProxy, AddressEditor>
implements HasRequestContext<AddressProxy>, IsWidget {
...
    @Override
    public Widget asWidget() {
return this.addressEditor;
    }
}
To be able to use this field as part of your view /uiBinder you have to extend isWidget and return the actual addressEditor (passed in as part of the constructor) via asWidget(). 
There are three possible scenarios here: New Person/New Address, Edit Person/New Address, Edit Person/Edit Address.
To be able to grab the address editor changes via the context, it seems trivial to create the AddressProxy (if non existent) prior to editing. The problem with this approach is that if you create a Person from scratch or if personProxy has no addressProxy already attached to it, and user don't fill out the address at all, you have already created an addressProxy which haunts you in validation (I don't know how to get around this one). Even returning null after flushing and before firing the context has no affect. 

To get around the validation issue, fake addressProxy seems to be the solution but If your validation resides on the server (like my scenario), there is no way to avoid actually saving the fake proxy. Your fake proxy needs to go all the way to the db since your sql carries the constraint as well and that means you have to interpret the fake values. 
In the client, upon rendering the "fake address proxy" recognize the fake value and empty out the fields. (setValue of the optionalFieldValue).

Thank you Thomas again, it has been a good learning process but with the limitations we have, I resort to removing all the constraint on the Address domain, at this point it seems to be the lesser of the evils. 
Reply all
Reply to author
Forward
0 new messages