PDA

View Full Version : ModelType alternatives for JSON stores



rynam0
20 Oct 2011, 5:15 PM
Hello,

I'm looking for some advice on implementing REST services and have so far been able to load JSON data using the examples. My obstacle right now is that I have a domain model that I would like to use in place of (re)defining a ModelType. As there is no support for Java reflection in GWT I have been unable to even create a Utility class that will generate ModelType instances for me from a POJO.

Are there any known ways to use a BeanModelMarker interface instead of a ModelType when working with JSON stores? Any and all information on this topic would be greatly appreciated as this is really the only blocker for me with the framework to date.

As an ExtJS user moving to GXT, I have to say that I am quite impressed and am enjoying the hell out of it. Keep it up!

Thanks,
-Ryan

Colin Alworth
21 Oct 2011, 8:00 AM
You are correct - what you are asking for cannot be done at runtime, using Ext GWT 2.

Ext GWT 3 (still pre-beta) supports GWT's AutoBean framework (http://code.google.com/p/google-web-toolkit/wiki/AutoBean) for sending and retrieving JSON and XML - the names of the getters and setters defined in the autobean interface describe the format of the JSON to come over the wire. Take a look at http://www.sencha.com/examples-dev/#ExamplePlace:jsongridexample to see how this can be put to use.

Your task can also be accomplished before runtime, a method that would probably need to be used for Ext GWT 2. One option would be to use something like an annotation processing tool, and generate code that would then be maintained in your source control system, and optionally edited. If that generated code isn't something you'd want to change, and you wouldn't want to see it in source control, then a much better option would be to build a GWT generator that would examine the POJO and create the ModelTypes for it dynamically (Note that this is how AutoBeans actually work). This is something of a complex topic that can't be adequately covered in a forum post, but there are lots of generators in the GWT code bases from which you can learn.

Another idea: you could build your own JSONReader class that uses the GWT JSONParser to turn the string into a JSONObject, and pass that into the JsonConverter.decode method. This should yield a Map<String, Object> which could then be passed into the BaseModelData constructor. Note that this will not work for nested objects, but for an object at a time you may find it useful. Clearly not ideal, but easier to get going with than the other two methods listed.

rynam0
21 Oct 2011, 1:48 PM
Thanks so much for your reply, Colin. Good information here for me to take this to the next level. The GWT Generator looks interesting to me so I think I'll start by taking a look into that option. I'll certainly let you know where I end up with this as I am certain I am not the only one interested in this topic.

Thanks again!

rynam0
24 Oct 2011, 7:55 AM
Colin:

A really quick proof-of-concept spike on GWT Generators yielded the following. It produces an Entity class that extends ModelType for use with JsonLoadResultReaders. As you are more experienced on this subject, could you possibly offer your opinion on actually using something like this in practice?


public class ModelTypeGenerator extends Generator
{
/** The fully qualified name of GXT ModelType.*/
private static final String MODELTYPE = "com.extjs.gxt.ui.client.data.ModelType";




@Override
public String generate(TreeLogger treeLogger, GeneratorContext generatorContext, String name)
throws UnableToCompleteException
{
JClassType classType;
TypeOracle typeOracle = generatorContext.getTypeOracle();
try
{
classType = typeOracle.getType(name);


String packageName = classType.getPackage().getName();
String generatedName = classType.getSimpleSourceName() + "ModelType";


ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(packageName, generatedName);
composer.setSuperclass(MODELTYPE);
composer.addImport(MODELTYPE);


PrintWriter pw = generatorContext.tryCreate(treeLogger, packageName, generatedName);
SourceWriter writer = composer.createSourceWriter(generatorContext, pw);


// create the constructor that adds fields to the generated ModelType subclass
writer.println("public " + generatedName + "() {");
writer.println("setRecordName(\"id\");");
for (JField field : classType.getFields())
{
writer.println("addField(\"" + field.getName() + "\");");
}
writer.println("}");
writer.commit(treeLogger);


return packageName + "." + generatedName;
}
catch (NotFoundException nfe)
{
nfe.printStackTrace();
}


return null;
}
}





ModelType personModelType = GWT.create(Person.class);
personModelType.setRoot("people");


JsonLoadResultReader<ListLoadResult<ModelData>> reader = new JsonLoadResultReader<ListLoadResult<ModelData>>(personModelType);



Thanks!
-Ryan

Colin Alworth
24 Oct 2011, 1:27 PM
The code seems to be reasonable, at least for the first step. The question I'd worry about is how would that ModelType actually be used to create a Person instance that implements ModelData?

I'm sure you also have a <generate-with> tag in your module that declares when this generator runs?

rynam0
24 Oct 2011, 4:41 PM
Thanks, Colin.
Yes, I currently have all my Entities extending a base class: BaseEntity and my "generate-with" looks for this type when applying the generator:



<generate-with class="com.rynam0.gxtrest.server.ModelTypeGenerator">
<when-type-assignable class="com.example.gxtrest.model.BaseEntity"/>
</generate-with>


Exactly how the following works is a bit of a mystery to me as I am not real clear on how we make the leap from ModelType to ModelData to BeanModel:


ModelType personModelType = GWT.create(Person.class);
personModelType.setRoot("people");


JsonLoadResultReader<ListLoadResult<ModelData>> reader = new JsonLoadResultReader<ListLoadResult<ModelData>>(personModelType);


RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, "services/people");

HttpProxy proxy = new HttpProxy(builder);

final BaseListLoader<ListLoadResult<ModelData>> loader =
new BaseListLoader<ListLoadResult<ModelData>>(proxy, reader);
loader.load();

ListStore<BeanModel> store = new ListStore<BeanModel>(loader);

Grid<BeanModel> peopleGrid = new Grid<BeanModel>(store, columnModel);

Colin Alworth
31 Oct 2011, 9:02 AM
BeanModel implements ModelData, so one can use a BeanModel instance anywhere a ModelData is expected. However in your case, I'm not sure you'll be using BeanModel at all.

ModelType is a way of describing which properties to copy to a ModelData instance, and JsonReader actually does the copying. JsonReader.newModelInstance() is responsible for actually creating the model, so if you want to create a new model object of your own type, wrap it in a BeanModel, and use that, that would be the place to do the wiring.

The JsonReader in Ext GWT 2.x is not designed to work with regular pojos directly, as I mentioned earlier. Subclassing JsonReader to write a custom newModelInstance, and your custom ModelType generator should get you most of the way there, or look into 3.x, and the AutoBean-based JsonReader implementation.

rynam0
3 Nov 2011, 5:46 AM
Thanks again, Colin...
The more I play with this, the more I think the AutoBean integration is where its at. We will indeed need support for nested objects and it looks like AutoBean will get us where we need to be with that. I'll take this for a test drive and let you know how things go.

Colin Alworth
3 Nov 2011, 5:54 AM
AutoBeans are pretty powerful, and should be able to give you what you are after. GXT has the JsonReader class to plug json data into the loaders, otherwise you can just stick with AutoBeanCodex for the most part. If you want to use XML instead though, GWT doesn't have anything that will give you support for it - try out XmlReader with its custom Splittable implementation.

If you have issues, we have a forum set up just for 3.0 bugs (http://www.sencha.com/forum/forumdisplay.php?84-Gxt-Bugs). There are a number of people who hang out in #extgwt on freenode - I'm there, as are a number of other users who are trying out GXT 3.

rynam0
3 Nov 2011, 6:28 AM
Great to know. Thanks alot!

rynam0
3 Nov 2011, 4:15 PM
So, I started to play around with the gxt-3.0.0-SNAPSHOT up on oss.sonatype repo but the API was so much different than 2.2.5 that I reverted instead of refactoring everything I had in place already. Anyway, I'll get back to testing that stuff later in a green project.

So, where I landed tonight was with a combination of AutoBean and BaseModelData. Probably not the most hugely performant code but I think it more or less gets me where I wanted to go w/out much fuss: JSON with nested objects in GXT 2.2.5. I've subclassed JsonLoadResultReader and used AutoBean to populate BaseModelData instances. This at least supports nested objects and requires an interface which if I'm thinking right, my domain models would implement to keep refactorings safe. I know the below implementation currently only supports arrays but that I can address later since this is just a proof of concept.


public class MyJsonLoadResultReader<D, T> extends JsonLoadResultReader<D>{
private AutoBeanFactory autoBeanFactory;
private Class<T> autoBeanInterface;




public MyJsonLoadResultReader(AutoBeanFactory autoBeanFactory, Class<T> autoBeanInterface)
{
super(null);
this.autoBeanFactory = autoBeanFactory;
this.autoBeanInterface = autoBeanInterface;
}




@Override
public D read(Object loadConfig, Object data)
{
JSONValue jsonRoot = JSONParser.parseStrict((String) data);
JSONArray root = jsonRoot.isArray();
ArrayList<ModelData> models = new ArrayList<ModelData>();
for (int i = 0; i < root.size(); i++)
{
JSONObject obj = (JSONObject) root.get(i);


AutoBean<T> bean = AutoBeanCodex.decode(autoBeanFactory, autoBeanInterface, obj.toString());
models.add(new BaseModelData(AutoBeanUtils.getAllProperties(bean)));
}


return (D) createReturnData(loadConfig, models, models.size());
}
}



Now to do something about the ColumnConfigs... I saw 3.0.0 introduced a new strategy for this but I'll need something for 2.5.5 too:



PersonAutoBeanFactory factory = GWT.create(PersonAutoBeanFactory.class);


RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, "api/people/list");
MyJsonLoadResultReader<ListLoadResult<BeanModel>, IPerson> reader =
new MyJsonLoadResultReader<ListLoadResult<BeanModel>, IPerson>(factory, IPerson.class);


HttpProxy proxy = new HttpProxy(builder);


final BaseListLoader<ListLoadResult<ModelData>> loader = new BaseListLoader<ListLoadResult<ModelData>>(proxy, reader);
loader.load();


List<ColumnConfig> columnConfigs = new ArrayList<ColumnConfig>();
columnConfigs.add(new ColumnConfig("firstName", "First Name", 200));
columnConfigs.add(new ColumnConfig("lastName", "Last Name", 200));
columnConfigs.add(new ColumnConfig("dob", "Date of Birth", 200));
columnConfigs.add(new ColumnConfig("addresses", "Addresses", 200));


ColumnModel columnModel = new ColumnModel(columnConfigs);


ListStore<ModelData> store = new ListStore<ModelData>(loader);
Grid<ModelData> peopleGrid = new Grid<ModelData>(store, columnModel);
peopleGrid.getView().setEmptyText("No people found.");
peopleGrid.setLoadMask(true);
peopleGrid.setHeight(400);

rynam0
4 Nov 2011, 5:26 AM
Yeah, I jumped the gun on this one... Nested objects still are not quite right (among other things). Back to the drawing board.

Colin Alworth
4 Nov 2011, 6:03 AM
We're shortly going to be heading deeper than you had intended to go with this... but try using the AutoBean.visit command to traverse the object and build out the ModelData instance with nested instances. GXT 3 has several AutoBeanVisitor examples you could take a look at to see how you might do this, and GWT has a few more internally. They aren't exactly light reading - I tend to be bad at reading iterative visitors, but I'm fairly certain its the best way to do what you are after.

I wouldn't be too terribly concerned about perf for the copying stuff itself - this shouldn't be much worse than O(n) on the size of your objects. A case like BeanModel doesn't do the copying up front, but does a lookup when the sub properties are actually requested, which in the long run can actually be worse.

I has assumed this was a green project from the introduction of json<=>beans, as the rest of your project probably has another way of solving this issue, or I wouldn't have suggested 3. GXT 3, for all the changes it makes and 2.x compatibility it breaks, still functions in the same basic ways, but is much more interoperable with mainstream GWT.

rynam0
4 Nov 2011, 6:31 AM
We're shortly going to be heading deeper than you had intended to go with this... but try using the AutoBean.visit command to traverse the object and build out the ModelData instance with nested instances. GXT 3 has several AutoBeanVisitor examples you could take a look at to see how you might do this, and GWT has a few more internally. They aren't exactly light reading - I tend to be bad at reading iterative visitors, but I'm fairly certain its the best way to do what you are after.

Haha, yeah I can see that. AutoBean.visit sounds like something I should take a glance at but I have to wonder: is there a target release date set for GXT3? Obviously you guys are already working on a more sound AutoBean integration and it would seem I am really just reinventing the wheel where that is concerned (even if it does target 2.2.5 instead).



I wouldn't be too terribly concerned about perf for the copying stuff itself - this shouldn't be much worse than O(n) on the size of your objects. A case like BeanModel doesn't do the copying up front, but does a lookup when the sub properties are actually requested, which in the long run can actually be worse.

Ok, good to know.



I has assumed this was a green project from the introduction of json<=>beans, as the rest of your project probably has another way of solving this issue, or I wouldn't have suggested 3. GXT 3, for all the changes it makes and 2.x compatibility it breaks, still functions in the same basic ways, but is much more interoperable with mainstream GWT.
Yeah, this is really a proof of concept that is helping me decide which way to go for future projects. To give you some background, I'm targeting EJB3 with JPA and want to keep the app as portable as possible and without introducing unecessary dependencies. I don't want to tie the team to a specific JPA provider and I haven't really been too excited about DTOs (when Hibernate is concerned). Gilead just rubs me wrong for a couple of reasons. I thought JAX-RS services might be the way to go forward.
I had enough UI written already that refactoring for GXT3 was enough of a pain to want to start a GXT3 proof of concept project separately. Package renaming and API changes made it difficult to quickly switch out 2.2.5 for 3.0.0. That being said, would it be worth waiting for 3.0.0 or is this quite a ways out?

As always, many thanks for your input!

rynam0
4 Nov 2011, 7:24 AM
GXT 3 has several AutoBeanVisitor examples you could take a look at to see how you might do this, and GWT has a few more internally.

Would you happen to know where I might find these? I'm not having much luck finding docs on AutoBeanVisitor.
Thanks!

Colin Alworth
4 Nov 2011, 7:55 AM
Like most of the really interesting (i.e. not drawing) stuff in GWT, it is almost totally without documentation. If using eclipse, there is a 'Find All References' (cmd-shift-g or ctrl-shift-g usually) command you can use to find all the subclasses. The "Type Hierarchy" view can also be useful for this.

That said, here are some examples. Warning, difficult reading ahead (assuming 2.4.0):
com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.PropertyCoderCreator - seems to track which beans have been seen yet, and watch which types need to be encoded/decoded
com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.PropertyGetter - traverses data and writes it out as json
com.google.web.bindery.requestfactory.server.Resolver.PropertyResolver - server only class! seems used to copy RequestFactory entities into proxy autobeans
com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext:879 - anonymous class to turn RF messages into data
com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext:1006 - anonymous class to clone autobeans - there is no non-deprecated built-in mechanism to do this, and this is designed for RF anyway, so grain of salt
com.google.web.bindery.autobean.shared.AutoBeanUtils:128 - walks a pair of beans, finding differences and returning them


GXT 3 contains a few too (taken from DP5), based on my reading of the GWT, and a number of unit tests:
com.sencha.gxt.data.client.writer.XmlWriter.PropertyGetter - turns an autobean into a xml document using some simple xpath properties
com.sencha.gxt.data.shared.writer.AutoBeanWriter:64 - used to wrap non-autobeans in an autobean so it can be passed through various autobean-writing mechanisms
com.sencha.gxt.data.client.writer.UrlEncodingWriter:66 - reads key:value pairs from an autobean and writes it out in a urlencoded fashion

Heavy reading, good luck :).

rynam0
4 Nov 2011, 11:21 AM
Colin: As always, you pointed me to exactly what I needed to make some more progress. The AutoBeanUtils source was what I've based my newest reader on and I've managed to actually end up with nested ModelData instances this way. Now if only there were a way to instead pass AutoBeans or interfaces as type parameters to Stores instead of ModelData I would be able to avoid the Visitor, casting, and string properties in calls like this:


(List<ModelData>) selected.get("addresses")

Anyway, thanks a million for all of your help. I've included my mostly working implementation below:



public class AutoBeanJsonLoadResultReader<D, T> extends JsonLoadResultReader<D>
{


private AutoBeanFactory autoBeanFactory;
private Class<T> autoBeanInterface;




public AutoBeanJsonLoadResultReader(AutoBeanFactory autoBeanFactory, Class<T> autoBeanInterface)
{
super(null);
this.autoBeanFactory = autoBeanFactory;
this.autoBeanInterface = autoBeanInterface;
}




@Override
public D read(Object loadConfig, Object data)
{
JSONValue jsonRoot = JSONParser.parseStrict((String) data);
JSONArray root = jsonRoot.isArray();


ArrayList<ModelData> models = new ArrayList<ModelData>();
for (int i = 0; i < root.size(); i++) {
JSONObject obj = (JSONObject) root.get(i);


AutoBean<T> bean = AutoBeanCodex.decode(autoBeanFactory, autoBeanInterface, obj.toString());
ModelDataAutoBeanVisitor visitor = new ModelDataAutoBeanVisitor();
bean.accept(visitor);


models.add(visitor.getModelData());
}


return (D) createReturnData(loadConfig, models, models.size());
}






/** An AutoBeanVisitor for AutoBean to ModelData conversion. */
private class ModelDataAutoBeanVisitor extends AutoBeanVisitor
{
private ModelData modelData;


public ModelDataAutoBeanVisitor()
{
this.modelData = new BaseModelData();
}
public ModelData getModelData()
{
return modelData;
}




@Override
public boolean visitValueProperty(String propertyName, Object previousValue, AutoBeanVisitor.PropertyContext ctx)
{
if (previousValue != null)
{
modelData.set(propertyName, previousValue);
}
return false;
}




@Override
public boolean visitCollectionProperty(String propertyName, AutoBean<Collection<?>> value, CollectionPropertyContext ctx)
{
if (value != null)
{
Collection<?> beans = value.as();
Collection<ModelData> models = new ArrayList<ModelData>(beans.size());
for (Object element : beans)
{
AutoBean autoBean = AutoBeanUtils.getAutoBean(element);


ModelDataAutoBeanVisitor newVisitor = new ModelDataAutoBeanVisitor();
autoBean.accept(newVisitor);
models.add(newVisitor.getModelData());
}


modelData.set(propertyName, models);
}
return false;
}
}
}

Colin Alworth
4 Nov 2011, 7:10 PM
Cool stuff, nice work. Two things:

> Now if only there were a way to instead pass AutoBeans or interfaces as type parameters to Stores

Check out 3, that aspect of the api is well settled, but as you've noted, there'll be a few changes to be made.

And second, it looks as though the root object going over the wire is an array. This can be a dangerous move to make. Two links to start looking into this (briefly, GET calls that return array without custom headers make some browser's vulnerable to handing away your user's data):

http://blog.frontend.fi/json-xss-vulnerability-with-top-level-arrays/
http://stackoverflow.com/questions/4353625/is-the-json-csrf-theft-attack-still-possible
It can be made safe, but I'd offer it isn't worth it. Also limits your upgrade path - AutoBeanCodex.decode cannot/will not support decoding an array - you'll need to do your trick here - json->array, iterate over each item, and item->json, then json->autobean. Ugly. As it appears you've already noticed.

Best practice is for the root of your data to be an object.

rynam0
5 Nov 2011, 4:41 AM
I've always wondered what the driving force was behind the named root object for collections but never did find a reasonable explanation. The links you provided clarifies this for me now. Much appreciated! This is a trivial change to make on the server side and now that I understand the importance I will make it standard. Thanks again.

I will certainly play with gxt3 but as we will be starting a new project this week it seems I have some work to do in regards to 2.2.5. You have been most helpful and I look forward to working with you some more!