I say almost, because the way DWR marshals your beans is either all or nothing. You can tell DWR, convert only these classes, and only these properties and that's it. What if you have different user roles that have different read permissions? e.g., detailed user info should be accessible to the admin and no one else. What if you want summary information when an object is nested inside another one? e.g., my Label BO has a name, description and a couple of flags associated with it. When I retrieve a labeled thing, say a message, I really only care about the label name so I can render a list of labels on the message. But when I retrieve a Label for editing, I need all the information about the label. Let's see if we can fix this.
Because DWR is open source, I can browse around and find the source for
BeanConverter
. It has a lot of hooks (including isAllowedByIncludeExcludeRules
, which would be helpful for role based filtering), but what we really need here is a hook to control what converter is used. Because we have the source code, why don't we add one? I'll just create a subclass of BeanConverter
and override convertOutbound
like so:
public OutboundVariable convertOutbound(Object data, OutboundContext outctx)
throws MarshallException {
Map ovs = new TreeMap();
ObjectOutboundVariable ov = new ObjectOutboundVariable(outctx);
outctx.put(data, ov);
try {
for (Entryentry : (Set >) getPropertyMapFromObject(
data, true, false).entrySet()) {
ovs.put((String) entry.getKey(), getPropertyValue(
(Property) entry.getValue(), data, outctx));
}
} catch (MarshallException ex) {
throw ex;
} catch (Exception ex) {
throw new MarshallException(data.getClass(), ex);
}
ov.init(ovs, getJavascript());
return ov;
}
The code is pretty much the same as in the superclass, but with 1 key difference. My version calls
getPropertyValue
. In the template class, I keep the default behavior, loading the converter from the converter manager, but we'll create a subclass in a moment that takes a different approach.First, let's look at what dwr.xml offers us in terms of configuration. It looks like you can set properties on converter instances using param elements. I prefer to use a concise format for configuration whenever possible, so our format will be something like "propertyName:includeExcludeProperty1,includeExcludeProperty2 otherProp:more,of,the,same". Supposing this was an include, then when we create the converter for the otherProp property, it will include the more,of,the and same properties and no others. Got it?
We need to provide parsing for our custom format in the converter:
public class SubBeanConverter extends TemplateBeanConverter {
protected MapsubInclude = new HashMap ();
protected MapsubExclude = new HashMap ();
public void setSubIncludes(String defs) {
this.subInclude = parseIncludeExclude(defs, true);
}
public void setSubExcludes(String defs) {
this.subExclude = parseIncludeExclude(defs, false);
}
private MapparseIncludeExclude(String defs,
boolean include) {
Mapmap = new HashMap ();
for (String def : defs.split("\\s")) {
String[] split = def.split(":", 2);
BeanConverter bc = new BeanConverter();
bc.setConverterManager(getConverterManager());
if (include) {
bc.setInclude(split[1]);
} else {
bc.setExclude(split[1]);
}
map.put(split[0].trim(), bc);
}
return map;
}
...
TemplateBeanConverter
is our original subclass of BeanConverter
. Since we're using the same format for includes and excludes, the same method is used to parse them both. For each property we list, we create a BeanConverter
instance that we will use in place of the usual converter.The last step is to override
getPropertyValue
and use our special BeanConverter
instances:
protected OutboundVariable getPropertyValue(Property property, Object data,
OutboundContext outctx) throws MarshallException {
Object value = property.getValue(data);
if (value != null) {
BeanConverter exConverter = subExclude.get(property.getName());
if (exConverter != null) {
return exConverter.convertOutbound(value, outctx);
}
BeanConverter inConverter = subInclude.get(property.getName());
if (inConverter != null) {
return inConverter.convertOutbound(value, outctx);
}
}
return super.getPropertyValue(property, data, outctx);
}
Easy right?
Next time I'll talk about my experience using Grails with DWR.
UPDATE: I mentioned role based filtering above, but I will leave that as an exercise to the reader. One hint: if you're using Acegi to control access to pages,
org.acegisecurity.context.SecurityContextHolder.getContext()will get you the list of granted authorities (AKA roles, e.g. ROLE_ANONYMOUS) for the current user.
.getAuthentication().getAuthorities()