Hi Jean-Baptiste,

Am 16.06.2009 um 14:47 schrieb Jean-Baptiste BRIAUD -- Novlog:

> On Jun 15, 2009, at 18:54 , Andreas Junghans wrote:
>
>> Hi Jean-Baptiste,
>>
>> Am 15.06.2009 um 17:21 schrieb Jean-Baptiste BRIAUD -- Novlog:
>>
>>> Why do I have to filter java.lang.Class or  
>>> java.lang.reflect.Method ?
>>
>> "class" is filtered out in the default implementation to avoid
>> unnecessary traffic and/or reference cycles.
> So, why do I have to filter it in my own filter was the real
> question :-) ?

You don't have to filter it yourself (at least that's how it should  
be). A call to super.filter() should be enough.

Maybe I should provide (still) some more background info about the  
filtering: The basic idea is to filter out certain attributes, not  
individual classes. The parameters passed to the filter method are (1)  
the object that's about to be converted and (2) a map of the object's  
properties (as determined by the RPC runtime). Let's say you have the  
following class:

public class MyBean {
   private int x;
   public void setX(int x) { this.x = x; }
   public int getX() { return x; }
}

If an instance of this class is returned by an RPC call, the filter  
method receives the following parameters:

- obj: The instance of MyBean
- map: A map containing the following keys and values:
   - x: 1234 (the value that's returned by calling getX() on the  
instance)
   - class: MyBean.class (because java.lang.Object provides a  
getClass() method)

The default implementation of filter() looks like this:

     protected Map filter(Object obj, Map map) {
         map.remove("class");
         return map;
     }

So the "class" attribute is removed, preventing it from being  
converted to JSON form. However, if you have a bean with a getFoo()  
method that returns a class object, the filter doesn't catch this. In  
this case, you'd have to remove "foo" from the map too. So the whole  
filtering process is based on attribute names, not class names.

>> The root of the problem
>> is that every Java object has a method "getClass" (which matches the
>> JavaBean pattern). So without filtering, the RPC implementation would
>> try to serialize the Class objects of all instances that are
>> transmitted. And since java.lang.Class has lots of getXYZ() methods
>> (some of them forming reference cycles IIRC), the JSON representation
>> quickly explodes ...
> I fully agree and because I had to filter it manually, I asked the
> question but now I guess I probably filter the wrong way !
> Here is how I did it, could you, please, revue my code :
>
>     protected RemoteCallUtils getRemoteCallUtils() {
>         return new RemoteCallUtils() {
>
>             protected Map filter(Object obj, Map map) {
>                 final String className = obj.getClass().getName();
>                 // System.out.println("----------- filter : " +
> className);
>                 if ("java.lang.reflect.Method".equals(className) ||
> "java.lang.Class".equals(className) )) {
>                     map.clear();
>                     return map;
>                 }
>                 removeUnwantedProperties(map); // This method remove
> my "user land" unwanted property
>                 return super.filter(obj, map);
>             }
>
> Should I do "my stuff" before or after the super.filter() ?

That doesn't matter. You can do your own filtering before or after (or  
not call the super class at all). Please note that in your  
implementation, you prevent Class objects from being transmitted, but  
not the associated properties. Again, let me provide an example:

public class MyBean {
   private int x;
   public void setX(int x) { this.x = x; }
   public int getX() { return x; }
   public Class getFoo() { ... }
}

With your imlementation, the generated JSON string looks something  
like this (provided you don't remove "foo" from the map in  
removeUnwantedProperties() - but in this case, you wouldn't need the  
extra check for java.lang.Class):

{ x: 1234, foo: {} }

The problem is that you're trying to filter by class name, but the  
original design is based on filtering by attribute name. There's  
nothing wrong with a class-based filter, but the current mechanism  
doesn't really support it. To completely get rid of certain classes,  
you'd have to iterate over the attribute map and check the classes of  
the values (instead of checking the class of the obj parameter).

>> You don't have to filter java.lang.reflect.Method, at least not when
>> you're transmitting simple objects. However, if there's a getXYZ()
>> method in one of your objects that returns a java.lang.reflect.Method
>> (or array thereof), then it should be filtered. Just look at the Java
>> API documentation of Method so see why: There's stuff like
>> getReturnType() that returns a Class. And regarding Class objects,  
>> see
>> above ...
> Same thing : I fully agree but I had to filter it in my own filter ...
>
>>
>> Maybe I should provide some background info about the filtering
>> mechanism. In general, when you try to convert Java objects to a
>> String representation, there are two choices:
>>
>> 1.) Explicitly specify which properties get serialized. This requires
>> some sort of meta data, e.g. the WSDL in case of web services. Or you
>> can put up a requirement that every object needs to implement a
>> specific serialization API (e.g. java.io.Serializable), which is also
>> some kind of meta data.
>>
>> 2.) Serialize all properties by default. In this case, you have to
>> provide a filter mechanism to avoid unnecessary traffic and reference
>> cycles (see above).
>>
>> Both approaches have their pros and cons.
> Same as before : I fully agree
>
>> qooxdoo's Java RPC
>> implementation uses the second one to ease development and  
>> deployment.
>> If you transmit simple objects, it works quite well. However, if you
>> take existing, complicated business objects, you may have to adjust
>> the filter. You could argue that the default filter list could be  
>> more
>> extensive, but this would be a never-ending story: Many JDK classes
>> are not easily serializable to a String, so you could end up with
>> thousands of filter entries that would be checked before each
>> transmission.
>>
>> You could also argue that only objects implementing
>> java.io.Serializable should be transmitted, but that would be very
>> annoying IMHO. Lots of objects in many libraries and existing
>> applications are not implementing Serializable, even when they can be
>> converted to JSON without issues.
>>
>> We could debate the different approaches all night long :-)  If you
>> don't agree with the one chosen in qooxdoos Java RPC implementation,
>> take a look at the other Java RPC libraries out there. Maybe you can
>> find one that suits your needs better (or mabye not - I don't know in
>> detail how the different libs handle serialization).
>>
>> One last remark about the filtering: Even when your transmission  
>> works
>> (no reference cycles), you should still take a look at the data
>> stream. In many cases, you can reduce the size of the JSON string
>> greatly by more aggressive filtering.
>
> True. That's why I'm thinking about adding one feature :
> Have both 1. and 2. approachs.
> Use annotation to tag some POJO at class level, say @C
> All the non @C classes will be kept with the current filtering  
> behavior
> For those who have the @C, and only that one, filter will keep only
> the taged property (at attribute or getter level), say @A.
> Only @A in the classes who is @C will be kept.
> Benefits :
> * for that cases, the filtering do not have to be dynamic. No need to
> check at runtime and everytime if the property is to keep or reject. A
> cache could be done for that since annotation won't change.
> * this would be a comprimise between 1 and 2 : have both, keep the
> current behavior if nothing is done but allow new behavior only if you
> want
> * handle well the case of existing business object.

Using annotations is a good idea. The current RPC code started out in  
a project where JDK 1.4 compatibility was required, so it wasn't  
possible to use annotations. This is also the reason why the  
RemoteService interface and the RemoteServiceException are used for  
tagging RPC services: It's more or less a workaround for the missing  
annotations.

However, JDK 1.4 doesn't seem particularly important anymore, so  
adding annotations could make things cleaner and easier.

> What about the case of a class tagged @C where you would like to keep
> the dynamic filtering ability ? Use @D.
> Any property (at getter level or attribute level) that would have @D
> (only for a @C class) will go to the filter and the answer to keep or
> reject could be done based on rules or any code in the user land (aka
> a subclass of the servlet like now)
>
> In summary, 3 anotations, no changes to existing code base, no change
> to existing behavior but a way tp have a static approach (with the
> hope or more speed) when needed.
>
> Example :
>
> @C
> public class Person {
>
>       @A
>       String name
>
>       String complicatedThingsOnlyForTheServer
>
>       @D
>       String ClientPromoCode
> }
>
> Because Person is @C, the "new" behavior is applied :
> * complicatedThingsOnlyForTheServer will never be JSONified, this
> become structural.
> * name will always be JSONified, this become structural.
> * ClientPromoCode may or may not be JSONified, depending on filter
> code in the RPC subclass.
>
> We may improve one thing to let the developper be as less as possible
> verbose :
> @A specify the property to kept, we could imagine to have the
> opposite : @R to reject a property.
> So, @C(defineR) will let you define the @R and the others are kept by
> default
> @C(defineA) will let you define the @A and the others are rejected by
> default
> @C is equal to @C(defineA)
>
> => depending on classes, one can only have to write the less !

In general, this is a good idea. However, I'd move @A and @R to the  
getter methods (and of course find proper names for them ;-)). After  
all, the transmitted values are determined by calling the getters, and  
you can have a getter with no directly associated member variable (or  
simply another naming standard like mName or _name).

I don't have the time to implement and test annotation support myself.  
But if you could provide a patch, I can hopefully review it and send  
you some feedback. Besides annotation support, an optional class-based  
filter (that also removes the attributes instead of creating an empty  
map) would also be useful. Oh, and you should take a look at Michael  
Wyraz' refactoring:

http://qooxdoo-contrib.svn.sourceforge.net/viewvc/qooxdoo-contrib/trunk/qooxdoo-contrib/RpcJava/branches/test/

It's a more modular implementation of the RPC code and also supports  
generics. Unfortunately, I never got around to give this code a proper  
review (shame on me!), but it's probably a better starting point for  
patches than the trunk version. Did I already mention that we badly  
need an active maintainer for the Java RPC stuff? Any candidates  
(Michael, Jean-Baptiste)? :-)

Regards,

   Andreas J.


------------------------------------------------------------------------------
Crystal Reports - New Free Runtime and 30 Day Trial
Check out the new simplified licensing option that enables unlimited
royalty-free distribution of the report engine for externally facing 
server and web deployment.
http://p.sf.net/sfu/businessobjects
_______________________________________________
qooxdoo-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel

Reply via email to