Refactoring RAP/RWT to Remove Class Variables
Lately I have started with the first of a set of RAP refactorings to ease the task of using RWT (RAP Widget Toolkit) as standalone widget library for web applications. With the term “standalone” I do not only refer to the possibility of running RWT in a standard servlet container without using the RCP workbench concept. I also think about the capability of using RWT inside an OSGi container or integrate with other runtime technologies like Spring.
RAP was basically created to enable RCP developers to build web applications reusing their knowledge and/or codebases. We have coined the term Single Sourcing for this. While RAP has been well adopted in the eclipse community there have always been folks using RWT to build web applications setting the workbench notion of RAP aside. This is quite understandable given the fact that the workbench concept forces you into a certain kind of UI paradigm that does not fit all use cases.
Unfortunately there are some difficulties buried in the depth of the library that hamper the standalone usage scenarios. One of those difficulties is the dated singleton structure used as implementation pattern for certain subsystems of the library. Consider for example multiple OSGi HttpServices in the same OSGi instance that run on different ports. Having RWT applications registered for at least two of those services would cause the singletons to be shared between those apps. This even may work to some extend, but it definitely opens up problems regarding the clean separation between applications on different ports. So I’ve opened a bug (#337787) targeting this problem and volunteered to provide a solution.
Looking into the code I have found over 20 singletons or classes with class variables. Although those classes are not API, but given the widespread use of e.g. the resource manager implementation, simply changing those classes into non singletons and provide a different approach of accessing them would have caused a huge rupture throughout the library’s codebase. So I had to find a less intrusive solution that allowed a step wise refactoring to get rid of the class variables.
Fortunately RWT comes with a comprehensive Testsuite that allows you to do substantial design changes without risking defects or compatibility breaches. So I felt comfortable to do a first “under the hood” refactoring step that reduced the static fields and singletons to exactly one singleton that would hold all the instances. In principle the idea looks like the following:
Singletons like
public class MySingleton {
private static MySingleton instance = new MySingleton();
public static MySingleton getInstance() {
return instance;
}
}
were refactored to
public class MySingleton {
public static MySingleton getInstance() {
return RWTContext.getInstance().getMySingleton();
}
}
public class RWTContext {
private static RWTContext instance = new RWTContext();
private MySingleton mySingleton = new MySingleton();
// [...] further singleton instances go here
public static RWTContext getInstance() {
return instance;
}
public MySingleton getMySingleton() {
return mySingleton;
}
// [...] further singleton accessors go here
}
The real implementation of RWTContext is somewhat more complex and uses currently a generic approach due to a problem of keeping the test suites alive. But the code above intends to give you the basic idea. Interested readers may have a look at the bug comments and the CVS history for more information.
Another problem that arose was that not all class variables represented singleton instances in terms of the pattern. Such variables were often simple maps used for buffering purposes controlled by a lot of static methods of the surrounding class. In this case the first step was to refactor the classes internally to match the singleton pattern:
Classes like
public class MyStaticClass {
private static Map buffer = new HashMap();
public static Type get( String key ) {
Type result = (Type )buffer.get( key );
if( result == null ) {
result = new Type();
buffer.put( key, result );
}
return result;
}
// [...] further methods
}
were refactored to
public class MyStaticClass {
private static MyStaticClass instance = new MyStaticClass();
private Map buffer = new HashMap();
public static Type get( String key ) {
return getInstance().doGet( key );
}
private Type doGet( String key ) {
Type result = (Type )buffer.get( key );
if( result == null ) {
result = new Type();
buffer.put( key, result );
}
return result;
}
private static MyStaticClass getInstance() {
return instance;
}
// [...] further methods
}
After that this could be refactored to move the instance to the RWTContext as explained above. Unfortunately this would led to classes that looks somewhat confusing having these ‘makeIt’ and ‘doMakeIt’ like method name pairs all over the place. To clean up the clutter a little bit I split those classes into two before refactoring the getInstance method:
public class MyStaticClass {
private static MyStaticClass instance = new MyStaticClassInstance();
public static Type get( String key ) {
return getInstance().get( key );
}
private static MyStaticClass getInstance() {
return instance;
}
// [...] further methods
}
class MyStaticClassInstance {
private Map buffer = new HashMap();
Type get( String key ) {
Type result = (Type )buffer.get( key );
if( result == null ) {
result = new Type();
buffer.put( key, result );
}
return result;
}
// [...] further methods
}
The next refactoring step was to change the singleton implementation of RWTContext to get a clean separation in case of running RWT in multiple http contexts or services on OSGi. To do so the RWTContext instance is buffered as attribute in the ServletContext instance at web context startup. This allows to use an separate instance for each web context. If a web context and with it the servlet context containing the RWTContext instance is destroyed there will be nothing left over of RWT.
But this left the question of how gaining access to the RWTContext within the library when you use one of the singletons we have successfully refactored to delegate to the RWTContext? Fortunately RWT has already a build in solution for this.
We have had a similar problem accessing instances of classes that are session scoped. To gain access to the session that belongs to the current request there is a class called ServiceContext. The idea behind this is simple: At the beginning of the request processing an instance of ServiceContext is tucked as thread local to the request thread. This instance is released at the end of the processing cycle. The service context is fed with the session, request, response and other useful things. A static accessor (ContextProvider.getContext()) allows us to access the service context throughout the request.
To solve the problem of RWTContext access the service context was enhanced to provide access to the RWTContext instance:
public class RWTContext {
private static RWTContext instance = new RWTContext();
public static RWTContext getInstance() {
ServiceContext context = ContextProvider.getContext();
return context.getRWTContext();
}
}
The real implementation here is also slightly more complicated, but basically follows the idea explained above. Internally the service context implementation uses the servlet API to retrieve the ServletContext that contains the RWTContext instance.
Conclusion
The changes described above are committed into CVS Head and seem to work like charm as far as we can see. The main work was done on a rainy weekend, which I judge as a fair amount of time with respect to what have been achieved. Removing the singletons from RWT did not only improve the possibility to use the library standalone. It also enabled some follow up refactorings of the library code itself. In particular it helped to improve the testing infrastructure streamlining the setup and teardown mechanism for most of the test cases.
However as I mentioned at the beginning this was only the first step of a set of refactorings. The next steps will address the way of how RWT is initialized, how it runs within OSGi, how it handles the UI Thread and more. So stay tuned, I’ll keep you posted :-)
- Xmas Clean Sheet Update (0.9) - 21. December 2021
- Clean Sheet Service Update (0.8) - 23. May 2020
- Clean Sheet Service Update (0.7) - 24. April 2020
Interesting reading. Is the RWTContext indented to be an interim class that will be removed later? How can clients store application specific data – as far as i remember ContextProvider is not public API.
We have already been discussing the first question within the team and we tend to let the RWTContext be the central data hub for application scoped objects. Furthermore Rüdiger proposed to remove the facades that originated with this refactoring and let the RWTContext provide the singleton instances directly. Ok, maybe we’ll have to find a better name for it – how about ApplicationContext?
We didn’t think about the second question yet, but I think it’s a legitimate one. From my current point of view I think we could add an additional scope managed by the RWT.class. It would look like
public static IApplicationStore getApplicationStore() {
// do the important stuff here
}