

// Client Side - What we're going for...
Customer customer = getSerializedObject("customer");
The first step to making the above happen is to capture the Customer object on the server side while we are building the page. This implies of course that you have a presentation layer or servlet feeding your pages and that it knows what the client needs. We use Struts 2 as a "glue" layer to help us build our GWT pages and so in our case this server side data capture happens in a Struts action class.// Server Side - Get the customer object
long customerId = ...;
Customer customer = new MyServiceImpl().getCustomerById( customerId );
The next step is to serialize the object.// Server Side - Serialize customer to a String
// ...
String serializedCustomer =
RPC.encodeResponseForSuccess( serviceMethod, customer, serializationPolicy );
The snippet above references our customer object as well as two new items that require explanation: The service method and serialization policy. The public API for serializing the object is the static method RPC.encodeResponseForSuccess(). This method requires a reference to the java.lang.reflect Method object representing our getCustomerById() interface method. It's not going to invoke the method (we've already done that), but it seems to require it simply to determine the declared return type. We can get the method reference like this:// Get the Method for encodeResponseForSuccess()
Method serviceMethod = MyService.class.getMethod( "getCustomerById", Long.TYPE )
Here we are asking the MyService class for a reference to our method. The first argument to getMethod() is the name of the method and what follows is a varargs list of parameter types. In this case the special field Long.TYPE represents the primitive long type. (Long.class would represent the Long wrapper type). If there were additional arguments we'd list their types here.// Server side - Get the SerializationPolicy for encodeResponseForSuccess()
// Note: You should cache the SerializationPolicyInputStream in =
GadgetAction.class.getResourceAsStream( "/gwt.rpc" );
SerializationPolicy serializationPolicy =
SerializationPolicyLoader.loadFromStream( in );
The difficulty I alluded to can be seen above. I cannot tell you how to identify the correct .gwt.rpc file as it will have a unique name based on the contents. At the moment this is a manual step in our build when the interface changes. I can think of a number of hacky ways to automate this step, but as far as I know there are only two good solutions (neither of which I have tried yet). One would be to write a GWT linker that participates in the build and can easily export a copy of the file under the correct name. I'm sure someone will respond to this posting by telling me how easy that is to do and I hope they also send an example :) Another way would be to simply implement a custom SerializationPolicy that is aware our class structure in a broader way. But such as custom policy would have to be aware of which standard types are allowed to be serialized as well as which custom types. Getting this wrong would not be a security issue in this case, but would certainly cause failures at runtime.<script type="text/javascript" language="javascript">
var customer='<serialized customer data>';
</script>
Before doing that substitution though, there is one last thing you must do. Your serialized data may contain characters that would be misinterpreted inside a single quoted javascript string. This is especially true if your data contains text with markup or HTML. To sanitize it we created the following helper method, escapeForSingleQuotedJavaScriptString():// Server Side String utils
public static String escapeForSingleQuotedJavaScriptString( String s )
{
s = escapeScriptTags( s ); // <script> -> <xxxscript>
s = escapeBackslash( s );
s = escapeSingleQuotes( s );
return s;
}
public static String escapeScriptTags( String s )
{
return s
.replaceAll("(?si)<\\s*script.*?>", "<xxxscript>")
.replaceAll("(?si)</\\s*script\\s*>", "</xxxscript>");
}
public static String escapeBackslash( String s )
{
return s.replaceAll("\\\\","\\\\\\\\" );
}
public static String escapeSingleQuotes( String s ) {
return s.replaceAll("'","\\\\'" );
}
First, the above escapes single quotes and backslashes to legal sequences inside the JavaScript string. Note that it is not necessary to "undo" this step as what we are doing is rewriting the quote and backslash so that it will be properly interpreted inside the string. Second, we are disabling any <script> tags that may appear in the quoted text by rewriting <script> as <xxxscript>. If you wish to pass script tags unmolested through to your client code then you would have to reverse this rewrite on the client side (e.g. create an unescapeScriptTag() method and use it on the client side).// Client side JSNI helper
public static native String getString(String name) /*-{
return eval("$wnd."+name);
}-*/;
If you have not used a JSNI method before, all that you need to know is that the "native" declaration and comment style wrapper indicate that the contents are JavaScript and not Java. The effect here is to fetch the JavaScript variable named in the argument and return it as a String.@SuppressWarnings("unchecked")
public static <T> T getSerializedObject( String name ) throws SerializationException
{
String serialized = getString( name );
SerializationStreamFactory ssf = GWT.create( MyService.class); // magic
return (T)ssf.createStreamReader( serialized ).readObject();
}
In this code we use our JSNI getString() method to retrieve the serialized string value. We then create a SerializationStreamFactory for our interface using the GWT.create() factory method. And finally we read the object. Here we have wrapped this all in a generic method so that it automatically performs a cast to whatever type we wish to assign it. Let's see the client usage again:// Client Side
Customer customer = getSerializedObject("customer");
Easy enough? Well, it depends on which side of the network you are working. When you are head down in GWT code and all you have to do is call getSerializedObject() to grab what you need it feels great. But there is work to be done on the server side to keep up the API changes. The necessity to refer to the method by name for the reflective lookup means that you can't currently abstract this approach as well as I'd like. It can also be difficult to debug where the problem is when you get a mysterious deserialization error. In the scenario I described it is easy to forget to update the gwt.rpc file when you add a new type to your service interface and that will cause an error right away. I'll let you know when I implement a better solution for this issue.Comment
Comment by Dan Moore on November 9, 2010 at 10:29am
Comment by Konstantin A Lukin on October 12, 2009 at 2:43pm
Comment by Cameron Souza on February 6, 2009 at 3:50pm
Comment by Pat Niemeyer on February 6, 2009 at 4:57am Could you, as part of your build process, have a small file that imports all of your custom types and then do a depth-first traversal starting from "Object" and adds to a list anything passing the "instanceof Serializable" test.
Comment by Daniel Leuck on February 6, 2009 at 3:38am
Comment by Nate Sanders on February 6, 2009 at 2:44am
Comment by Daniel Leuck on February 4, 2009 at 12:01pm
Comment by Nate Sanders on February 4, 2009 at 11:07am © 2013 Created by Daniel Leuck.
You need to be a member of TechHui to add comments!
Join TechHui