PDA

View Full Version : ValueProvider+RequestFactory - how to access properties of nested object?



IgyBoy
2 Feb 2012, 10:28 AM
Hi everybody,

i'm trying to figure out how to display properties from related object in a grid.
Let's say I have entity object called "Song",that is relating to "Genre" object.

Now,what I want to achieve is to display list of "Song" objects on grid,but also include some of the details from related "Genre" object.
From what I read on GXT 3.0, valueProviders should be able to do that, but I haven't found out how to do it.

So far,I have managed to do this without problem:

interface SongProxyProperties extends PropertyAccess<SongProxy> {

ModelKeyProvider<SongProxy> id();
ValueProvider<SongProxy, String> artist();
.
.
.
ValueProvider<GenreProxy, String> genre(); ?????????
}


Can anyone help me with this?

Tnx,
Igor

IgyBoy
4 Feb 2012, 1:31 AM
Never mind, figured it out - have read about GWT Editor framework a bit...

koenjan
22 Feb 2012, 4:10 AM
Hey Igor,

Could you tell us how you did it? We are also looking for the same thing, but reading GWT Editor docs did not make us any wiser :) ...

Thanks,
Koen

IgyBoy
22 Feb 2012, 4:48 AM
Hi koenjan,

the "@Path" annotation is what you are looking for:
From the GWT docs:

"The @Path annotation may be used on the field or accessor method to specify a dotted property path or to bypass the implicit naming convention. For example:
class PersonEditor implements Editor<Person> {
// Corresponds to person.getManager().getName()
@Path("manager.name");
Label managerName;
}


So, in the example that I had above:

interface SongProxyProperties extends PropertyAccess<SongProxy> {
ModelKeyProvider<SongProxy> id();
ValueProvider<SongProxy, String> artist();
@Path("genre.genreName")
ValueProvider<SongProxy, String> songGenre();
}

The @Path("genre.genreName") is basically saying: take "genreName" property from related "genre" object, and expose it as "songGenre" in SongProxyProperties interface.

Hope this was helpfull.

Cheers,
Igor

koenjan
22 Feb 2012, 6:28 AM
Thank you for your response...
In fact, we already tried the @Path annotation, but somehow we got NullPointerExceptions...

after some debugging, we found that to be able to use

@Path("type.name")
ValueProvider<DetailProxy, String> typeName;

that instead of

detailRequest.get().find(requestId).fire(receiver<DetailProxy>) which did not include the <DetailTypeProxy> inside the <DetailProxy>,
we had to do

detailRequest.get().find(requestId).with("type").fire(receiver<DetailProxy>)

maybe this is useful for others...

Adam.Elkins
19 Oct 2012, 10:51 AM
I am having a very similar issue. I am using RequestFactory with Hibernate to populate a Sencha grid with a paging toolbar. By using the path annotation I was able to successfully populate the grid with data. I then tried to implement the paging toolbar which requires the use of RequestFactoryProxy and am now receiving a NullPointerException for the Columns in the grid that access nested objects. It seems that the use of the with() method within the context of RequestFactoryProxy doesn't work as expected.

ramgovind
4 Dec 2012, 9:04 PM
How do we prevent from throwing null pointer exception of ValueProvider

IgyBoy
5 Dec 2012, 4:17 AM
@Adam.Elkins:
Check out on this thread:
http://www.sencha.com/forum/showthread.php?247769-Paging-grid-with-RequestFactory-and-related-entity-doesn-t-load-related-entity

I had the same problem,so it might be helpful to you.

Colin Alworth
5 Dec 2012, 7:16 AM
Let say we have a Person, and Person.getAddress() returns an Address object, and Address.getCity() returns a String city that person lives in. We might use this PropertyAccess:



interface PersonProperties extends PropertyAccess<Person> {
@Path("address.city")
ValueProvider<Person, String> city();
}

In this case, we'll be able to use that ValueProvider to get and set the city of the address of the person as if we were writing these lines of code:


person.getAddress().getCity()

person.getAddress().setCity(...)


But what happens if the address is null? It can't magically know that you don't want that to be null, that you intend for it to have a non-null value, and it can't create a new Address object for you - in that case, what should getCity do? Even if we rebuild that (and this doesn't make sense to me) to return null, what can setCity do when the user types in a value, for example, in a grid?

There is no way for us to create those sub-objects in a consistent way - what if you need a StreetAddress subclass of the abstract Address class? What if Address is a RequestFactory proxy, and thus only an interface? What if you must use a non-default constructor to build Address? Any of these make it hard to generate code that builds your sub-objects for you.

If you want custom behavior in your ValueProvider, built a custom implementation of the interface. PropertyAccess is only for simple generated default behavior.



class PersonWithNullableAddressCityValueProvider
implements ValueProvider<Person, String> {
public String getValue(Person object) {
return object.getAddress() == null ? null : object.getAddress().getCity();
}
public void setValue(Person object, String value) {
if (person.getAddress() == null) {
person.setAddress(new Address());
}
person.getAddress().setCity(value);
}
}

ramgovind
5 Dec 2012, 8:05 AM
Cool! Appreciated all your support.

ramgovind
5 Dec 2012, 10:31 AM
class ClientWithNullableParentKeyValueProvider
implements ValueProvider<PersonProxy, Long>
{
PersonProxy object;
ClientWithNullableParentKeyValueProvider(PersonProxy object){
this.object=object;
}

@Override
public Long getValue(PersonProxy object) {
return object.getParentKey() == null ? null : object.getParent().getParentKey();
}


@Override
public void setValue(PersonProxy object, Long value) {
if (object.getParent() == null) {
object.setParent(cproxy);
}
object.getParent().setParentKey(value);
}


@Override
public String getPath() {
return "parent.parentKey";
}
}Its working with Proxy.

Real credit goes to Colin!

gersonjohan
16 Mar 2013, 8:19 PM
Hi Colin,

I think that at least for the case of reading (get), the code generator must validate is parent objects are null.

I apreciate if you include this in a future release.

Colin Alworth
21 Mar 2013, 2:55 PM
Short answer: null checks seem like an obvious win, but aren't. They make for ugly generated code, inconsistent results, and are a bit nasty to futher customize.

Long answer, brace yourself:

We avoided this in the PropertyAccess implementation for several reasons. First, this starts down the road of customization that we didn't want to entertain - if you want to build a feature like this, you can either modify/extend our generator classes, or write ValueProviders by hand (or generate them in some other way). We've also recieved requests for some default value instead of null - say, if parent is null, then parent.name should return "no parent on file" - this is more customization that doesn't belong in code like this. Second, this will certainly cause the compiler to emit more complex code - instead of just reading out the property value, now we need logic just to get values like this. In many cases, these ValueProviders compile down to just direct field access:

(obj.parent.name)
//instead of
(obj.getParent().getName())

so adding null checks at each step end up as

(obj == null ? null : (obj.parent == null ? null : obj.parent.name))

Clearly this would be almost twice as complex for each new possibly-null step in the @Path chain. At present, we still consider the costs here to outweigh the benefits.

Now, lets look at the other half of ValueProvider: setValue. What should setValue do in your case if parent is null? Throw away the value? Continue to throw an exception (i.e. back to non-'null safe' behavior)? Automatically create a new Person object and invoke the parent setter? The first sounds a lot like 'fail silently', and the last ends up turning

obj.parent.name = val

into

(obj.parent == null && obj.parent = new Person()), obj.parent.name = val

This then assumes that all classes that might be used in such a way a) have default/public constructors, and b) make sense to create like this.

If you are interested in customizing this behavior, ValueProvider generation is covered by the ValueProviderCreator class. This is invoked by PropertyAccessGenerator, as well as the XTemplateGenerator when either of those need paths to be traversed into values. There is also a LabelProviderCreator class and a ModelKeyProviderCreator class that deal with their own tasks - though a null key is never safe to use within the GXT classes that use ModelKeyProvider.

ValueProviderCreator.getGetterExpression(String) is responsible for generating a Java expression that can read out the value from within the given object expression. If you want to use statements instead of expressions, consider taking over at a higher level, at appendGetterBody. Once you've created your own subclass, you can copy the PropertyAccessGenerator, and reference your creator instead of ValueProviderCreator. Finally, instruct GWT to use your custom generator when building PropertyAccess types with a generate-with rule, something like:

<generate-with class="com.project.rebind.NullPropertyAccessGenerator">
<when-type-assignable class="com.sencha.gxt.data.shared.PropertyAccess" />
</generate-with>

Just make sure this rule is after any inherits statement that pulls in GXT, and it will take over for property access generation.

if you aren't interested in modifying or forking the PropertyAccess generation (perhaps with a new interface like NullSafePropertyAccess?), here's another idea - a NullSafeValueProvider wrapper around one to many ValueProviders. Something like this - start with a PropertyAccess that emits ValueProviders without "." in them, and compose them like this:


ValueProvider<Company, String> ownerFirstName = NullSafeValueProvider.buildWith(companyProps.owner()).and(personProps.name()).and(nameProps.firstName()).done();

Where companyProps is a PropertyAccess<Company>, personProps is a PropertyAccess<Person>, and nameProps is a PropertyAccess<Name>, each with the given getters/setters. Here is the quick NullSafeValueProvider class and builder code I just threw together - the builder looks extra verbose, but going with this approach should ensure type safety - that each chained ValueProvider matches the one before it:

public class NullSafeValueProvider<T,V> implements ValueProvider<T, V> {
public static class IntermediateBuilder<T,V> {
private List<ValueProvider<?, ?>> providers;
private IntermediateBuilder(List<ValueProvider<?,?>> providers) {
this.providers = providers;
}
public <U> IntermediateBuilder<T,U> and(ValueProvider<V,U> next) {
providers.add(next);
return new IntermediateBuilder<T, U>(providers);
}
public ValueProvider<T,V> done() {
return new NullSafeValueProvider<T, V>(providers);
}
}
public static <T,V> IntermediateBuilder<T,V> buildFrom(ValueProvider<T,V> initial) {
List<ValueProvider<?,?>> rawProviders = new ArrayList<ValueProvider<?,?>>();
rawProviders.add(initial);
return new IntermediateBuilder<T,V>(rawProviders);
}
private final List<ValueProvider<?,?>> providers;
private NullSafeValueProvider(List<ValueProvider<?, ?>> providers) {
this.providers = providers;
}
@Override
public String getPath() {
StringBuilder sb = new StringBuilder();
for (ValueProvider<?, ?> vp : providers) {
if (sb.length() != 0) {
sb.append(".");
}
sb.append(vp.getPath());
}
return sb.toString();
}
@SuppressWarnings({"unchecked", "rawtypes"})
public V getValue(T object) {
Object result = object;
for (ValueProvider vp : providers) {
if (result == null) {
return null;
}
result = vp.getValue(result);
}
return (V) result;
}
@SuppressWarnings({"rawtypes", "unchecked"})
public void setValue(T object, V value) {
//This is currently written to fail if you hit a null before the final object, but
//could also be changed to emit a warning, or fail silently
Object actual = object;
for (ValueProvider vp : providers.subList(0, providers.size() - 1)) {
if (actual == null) {
throw new NullPointerException(getPath());
}
actual = vp.getValue(actual);
}
((ValueProvider)providers.get(providers.size() - 1)).setValue(actual, value);
}
}


Untested (and unsupported), but it should allow for a clean API when chaining together existing valueproviders, checking for nulls at the end of each. At present, we don't intend to add this to the library