PDA

View Full Version : Mapping GWT RequestFactory proxies to GXT components



mxhn
1 Mar 2011, 11:35 PM
I'm trying to use TreeLoader and TreeBeanModelReader to directly map the returned beans from server. This is the code:


TreeBeanModelReader tbmr = new TreeBeanModelReader();
BaseTreeLoader<ModelData> treeLoader = new BaseTreeLoader<ModelData>(tbmr);

TreeStore<ModelData> store = new TreeStore<ModelData>(treeLoader);
treePanel = new TreePanel(store);

//do a Request to return a list of CityProxy that implement BeanModelTag
req.getAllCities(cp, "extra_param_value").fire(
new Receiver<List<CityProxy>>() {

@Override
public void onSuccess(List<CityProxy> cities) {
treeLoader.load(data);
}

});

The issue is that the returned list cities doesn't contain instances of CityProxy but instances of CityProxyAutoBean_com_google_gwt_requestfactory_shared_impl_EntityProxyCategory_com_google_gwt_requestfactory_shared_impl_ValueProxyCategory_com_google_gwt_requestfactory_shared_impl_BaseProxyCategory extends com.google.gwt.autobean.shared.impl.AbstractAutoBean<com.tion.shared.CityProxy>.

All these instances are not implementing BeanModelTag since only my proxy, CityProxy implements it. TreeBeanModelReader tries to do this:


BeanModelFactory factory = BeanModelLookup.get().getFactory(beans.get(0).getClass()); and it returns NULL because beans.get(0).getClass(), which returns the generated AutoBean class above, doesn't implement BeanModelTag.


Is there any way to make this work?


Thank you!

mxhn
3 Mar 2011, 12:50 AM
No ideas? Anyone?

Colin Alworth
3 Mar 2011, 2:07 PM
RequestFactory is a new GWT feature that relies on knowing everything there is to know about the models it mocks at compile-time, whereas the current version of GXT allows for models to be somewhat more flexible, though admittedly this is not taken advantage of for BeanModels. This difference is what leads to the issue you are encountering - GXT expects to find types that have the BeanModelTag marker interface, and GWT's RF generators don't know to add that interface.

That being said, it appears someone has come up with the changes required to make this possible with current versions of GXT. http://www.sencha.com/forum/showthread.php?122282-BeanModel-generation-with-AutoBean-support

Full support for RequestFactory and AutoBeans should be available in the next version of GXT.

Edit: Well now I feel stupid – you are the other individual in that other thread... Digging a little further, I'll reply again later if I come up with anything that can help.

Colin Alworth
3 Mar 2011, 2:24 PM
Okay, looking more precisely at your issue with TreeBeanModelReader, the issue appears to be that it is trying too hard to get too precise of an instance of the factory, when you think it should settle for one further away.

The basic issue can be explained as this - If I make a model object, and tag it with the marker interface, then subclass it but do _not_ tag the subclass, the present system does not hunt it down and built it an factory - you didn't indicate that you wanted one, so it didnt make one. One can argue whether or not doing so would be a good idea on the merits of being rigorous or a bad one as it could make for some unnecessarily large code, but the point still stands - if you ask it to obtain a factory instance for you, it tries to be very precise.

My suggestion to you would be to not use TreeBeanModelReader as is, but copy and change it, replacing the possibilities for loading a factory with a factory instance that you pass in. This way, when you create a factory, you will say
TreeAutoBeanModalReader reader = new ...(BeanModelLookup.get().getFactory(MyModelProxy.class);
Using that reader will ensure that you are always using the correct factory instance. Note that RequestFactory at this time does not appear to support polymorphism, so you shouldn't have an issue with the wrong subtype of MyModelProxy being requested.

The read method in this TreeBeanModelReader could look like this:


public List<ModelData> read(Object loadConfig, Object data) {
if (data instanceof List) {
List<Object> beans = (List) data;
if (beans.size() > 0) {
return (List) factory.createModel(beans);
}
return (List) beans;

}
where factory is the factory passed in through the constructor.

As in my last post, RequestFactory entities and AutoBean instances will be supported natively in the next version of GXT, so this will no longer be necessary. Sorry for the confusion in my first post, I am not generally in the habit of checking user names across various posts, but I will be now...

mxhn
7 Mar 2011, 2:19 AM
Hello Colin,

Thank you for taking the time and reply on my issue. I appreciate it.

Until now I've tried to extend BeanModelGenerator with a custom generator that generates an implementation of BeanModelLookupImpl. This BeanModelLookupImpl would test the super-interface of the given class to getFactory. If it finds that class implements an interface extended from BeanModelTag then it would return an appropriate factory.
This was my strategy and it seemed sound from the start, but unfortunatelly I've learned that GWT doesn't have a Class.getInterfaces() method implemented yet. So it's not possible to implement my idea.

As of this moment, the implemented interfaces of a given GWT class can only be found out inside a Generator while at code generation stage. Since the BeanModelLookupImpl itself and the proxy implementations are generated by a generator, it is not possible to get the class parameter given to BeanModelLookupImpl.getFactory(Class clazz) and provide it to the generator. Nor it is possible to get all implementing generated classes of the proxy interface inside the generator because they're generated and don't exist statically(i.e. they're not included in the TypeOracle).

Your idea sounds like a good way to go if the client would know that all beans returned are of the same type. But in a tree-like structure it is possible that you could have something like:
* Folder [1]
** Melody
** Folder
*** Melody

so that the tree consists of multiple types of beans. If you expand the [1] folder, the list returned from server would contain different types. These would be returned as: {AutoBean_MelodyProxy, AutoBean_FolderProxy}. The issue here is that the client can't know which interface AutoBean_MelodyProxy implements, hence I don't know how to provide a factory like you said for this case. Since these classes are automatically generated by GWT's RequestFactory generator, I can't create a map like {AutoBean_MelodyProxy->MelodyProxy, AutoBean_FolderProxy->FolderProxy} and pass it to TreeBeanModelReader. One way perhaps to do this would be to have my server beans encode for each bean a property that specifies the name of the Proxy(i.e. getProxyName() -> FolderProxy). I'm not sure if you considered this case but if you did, I hope you could provide me with a bit more insight.

Since I'm not a premium user yet, could you give me a rough estimate when the next GXT version will come out with RequestFactory and AutoBean support?

Thank you!

mxhn
7 Mar 2011, 5:28 AM
Giving it a another thought, I don't know if it makes sense to go down this "reinvent the wheel" road.

I've talked with @Stigrv (http://www.sencha.com/forum/member.php?149869-stigrv) in this thread (http://www.sencha.com/forum/showthread.php?122282-BeanModel-generation-with-AutoBean-support/page2) and while he said he upgraded to RequestFactory I don't have a clear understanding of how much work is involved.

Even if I'd fix the above thing, I'd still have to modify the BaseLoader which uses this reader into something that works with RequestFactory. Now, BaseLoader uses the DataProxy interface which uses AsyncCallback that is RPC specific.

If you could help me with this I'd appreciate it very much.

Thank you!

Colin Alworth
7 Mar 2011, 7:37 AM
I'm going to try to reply to a lot of things here, not just your last reply, because there are lots of options available.

This BeanModelLookupImpl would test the super-interface of the given class to getFactory. If it finds that class implements an interface extended from BeanModelTag then it would return an appropriate factory.
This was my strategy and it seemed sound from the start, but unfortunatelly I've learned that GWT doesn't have a Class.getInterfaces() method implemented yet. So it's not possible to implement my idea.

While this is true at runtime (Class<?> data takes a ton of space, and including it at runtime is a great way to make your app huge), instanceof still works. Your approach here could be somewhat expensive though, at runtime looking at each supertype and interface... Something like this should belong in the generator, if at all.


As of this moment, the implemented interfaces of a given GWT class can only be found out inside a Generator while at code generation stage. Since the BeanModelLookupImpl itself and the proxy implementations are generated by a generator, it is not possible to get the class parameter given to BeanModelLookupImpl.getFactory(Class clazz) and provide it to the generator. Nor it is possible to get all implementing generated classes of the proxy interface inside the generator because they're generated and don't exist statically(i.e. they're not included in the TypeOracle).
All correct, but you are still missing that this system is designed to be used at runtime, against the types it knows. The bean model wrapping/unwrapping code is somewhat stronger than RequestFactory or AutoBeans, as it can work with any set of classes, no matter the inheritance, not just interfaces. As a result of this, you must be more precise when referring to it at runtime – when getting a factory, as I mentioned, you must ask for the correct one.


Your idea sounds like a good way to go if the client would know that all beans returned are of the same type. But in a tree-like structure it is possible that you could have something like:
* Folder [1]
** Melody
** Folder
*** Melody

so that the tree consists of multiple types of beans. If you expand the [1] folder, the list returned from server would contain different types. These would be returned as: {AutoBean_MelodyProxy, AutoBean_FolderProxy}. The issue here is that the client can't know which interface AutoBean_MelodyProxy implements, hence I don't know how to provide a factory like you said for this case. Since these classes are automatically generated by GWT's RequestFactory generator, I can't create a map like {AutoBean_MelodyProxy->MelodyProxy, AutoBean_FolderProxy->FolderProxy} and pass it to TreeBeanModelReader. One way perhaps to do this would be to have my server beans encode for each bean a property that specifies the name of the Proxy(i.e. getProxyName() -> FolderProxy). I'm not sure if you considered this case but if you did, I hope you could provide me with a bit more insight.
Unfortunately, you are correct here – that is the big issue with the code I provided (and why my comment above doesn't help you).

It gets a little deeper than this - BeanModelFactory.newInstance() is supposed to be able to construct new instances of the types it manages, which is clearly not possible out of the box when wrapping interfaces that will be implemented at runtime. Truly, this was intended for bean classes, and the new RF and AB stuff is a totally different mechanism.

Before moving on, I'd like to point out a solution that still uses the idea I gave before, and fits the use case you just mentioned. Instead of passing in a single factory to a general RF-ready treereader, make a specialized treereader that expects only your two types. Have it then verify that any given instance is a Folder or Melody subclass before selecting the proper factory for it. I can help with the code if you don't yet see what I mean.


I've talked with @Stigrv (http://www.sencha.com/forum/member.php?149869-stigrv) in this thread (http://www.sencha.com/forum/showthread.php?122282-BeanModel-generation-with-AutoBean-support/page2) and while he said he upgraded to RequestFactory I don't have a clear understanding of how much work is involved.

Even if I'd fix the above thing, I'd still have to modify the BaseLoader which uses this reader into something that works with RequestFactory. Now, BaseLoader uses the DataProxy interface which uses AsyncCallback that is RPC specific.
I think it is not that bad – as far as I can tell, BaseLoader just makes an AsyncCallback, and passes it to its proxy. If you were to extend any loader you want to pass a Receiver instead, and make a new DataProxy like object (or go the other way - make DataProxy wrap the AsyncCallback in a Receiver), you should be just fine. Nothing else in the store or loader are dependent on RPC except for the use of that success/fail interface.

If you are using the loader/proxy to get data, there shouldn't be issues with violations going off, so just have receiver.onSuccess call asyncCallback.onSuccess, and the same for onFailure, plus some exception unwrapping code if you like.

One final thought –
final MyRequestFactory rf = GWT.create(MyReqyestFactory.class);
rf.startMyRequest().loadSomeData().fire(new Receiver(){
public void onSuccess(MyDataModel model) {
EntityProxyId<MyDataModel> id = model.stableId();
//what does this return:
id.getProxyClass();
}
});
If, as I suspect, this returns the interface that the EntityProxyImpl was created to wrap, you can pass that to getFactory(Class), and always get the correct factory. I make this assumption because:
AbstractRequestContext (the superclass to any MyRequest interface) has create(Class), which calls
IdFactory.allocateId (IDFactory is a superclass of AbstractRequestFactory), which calls
IdFactory.createId(Class, int), which calls
new SimpleProxyId(Class, int), which sets the SimpleProxyId's proxyClass field to that class object.

This may only apply for creating new instances, but that would surprise me, as you can turn strings into instances with a little extra type info. Further, you can get history tokens from RF that refer to the proxy interface.

Good luck! From the SenchaCon 2010 slides (http://www.slideshare.net/senchainc/keynote-abesunnight), GXT 3 is expected this summer, but that is all I know for certain.

mxhn
7 Mar 2011, 7:48 AM
Thank you! You wrote a very comprehensive reply.

I'll try to understand all you said, and I'll come with feedback later if anything's unclear.

I appreciate it very much! Have a great day!

Colin Alworth
7 Mar 2011, 7:49 AM
Stop by #extgwt on irc.freenode.net if you want to chat.

mxhn
7 Mar 2011, 7:51 AM
Sure, thank you!

Algiano
24 Jul 2011, 11:29 AM
Hi mxhn,

I was wondering if you found a solution to the problem. I'm facing a very similar issue to the one your described here:


This is because GXT tries to get the BeanModel factory by using BeanModelFactory factory = BeanModelLookup.get().getFactory(beans.get(0).getClass()); and this returns NULL. GWT's RequestFactory generator takes the proxy interfaces(that include the ones tagged with BeanModelTag) and generates an AutoBean implementation but which isn't tagged directly with BeanModelTag. This implementation is not found by TreeBeanModelReader which returns an assertion error for such beans.

In fact it's the same issue and I get the same Assertion error. I implemented the AutoBeanModelGenerator as explained by stiv but the assertion error persists because of the BeanModelTag issue you mentioned above.

Did you find a way around this issue?

Thanks,
Ale

mxhn
24 Jul 2011, 11:45 AM
Hi Algiano,

I think a workaround is to pass the class of your proxy in the constructor of the reader and consider all beans are of the same type. In my case I considered this an appropriate solution, and hence I didn't try to find a solution that considers multiple bean types in the returned array.

I've attached my current reader code so you can get an idea. I hope it helps


public class MyTreeBeanModelReader extends TreeBeanModelReader {

protected Class beanTypes;

public MyTreeBeanModelReader(Class beanTypes) {
super();
this.beanTypes = beanTypes;
}

@SuppressWarnings("rawtypes")
protected Class getBeanClass(Object bean) {
return this.beanTypes;
}

/*
* (non-Javadoc)
*
* @see
* com.extjs.gxt.ui.client.data.TreeBeanModelReader#read(java.lang.Object,
* java.lang.Object)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public List<ModelData> read(Object loadConfig, Object data) {
if (data instanceof List) {
List<Object> beans = (List) data;
if (beans.size() > 0) {
if (isFactoryForEachBean()) {
List models = new ArrayList(beans.size());
for (Object o : beans) {
Class cls = getBeanClass(o);
BeanModelFactory factory = BeanModelLookup.get().getFactory(cls);
assert factory != null : "No BeanModelFactory found for "+ cls;
models.add(factory.createModel(o));
}
return models;
} else {
Class cls = getBeanClass(beans.get(0));
BeanModelFactory factory = BeanModelLookup.get()
.getFactory(cls);
assert factory != null : "No BeanModelFactory found for "+ cls;
return (List) factory.createModel(beans);
}
}
return (List) beans;

}

assert false : "Error converting data";

return null;
}

}