In our project we need to provide some supporting GIS functionality via an Applet GUI to our web based CRUD operations. Users, for example, may populate search criteria, entering input both from web interface, like name pattern, date range etc., and from Applet GUI, like selecting a specific region in a map, and then execute their queries and display results in both sides. That is, results will show up as rows in a data table on web page, and as some funny icons on Applet.
There is a need to coordinate user operations and share data entered on both sides during execution of those scenarios. We decided to construct a server side context sharing mechanism. Let’s take searching as an example:
When a user opens a search page in our web application, we create automatically a new context, bound to its user session. There are two tab views in the search web page, in one view user interacts with web components, and in the other view, user interacts with Applet GUI. He may enter some part of search criteria from web side and then switch to Applet view and select interested query region, and then select back to the web view and execute query with the prepared criteria. Both views make use of context in user session to prepare search criteria. More specifically, in Applet view, user selects search region, applet gets context and put selected region information into the context. Later, when user switches back to web view, web view accesses context and fetches this search region and uses it as part of search criteria, and initiates search. When results are fetched, they are put into the context, as well, in order for Applet view to be able to display them. Hence, results are shown in web view as normal html table, and when user switches to Applet view, they are fetched from shared context and drawn as icons on a GIS map.
There are several important constructs to realize this architecture. First of all, there is a context service to let both views to access context and operate on it on the server side. Second, there is a need to enable Applet-Web application communication, and finally keep context alive between user requests, as user scenarios may span several web requests.
public interface IGISContextService { public IGISContext createContext(int contextType); public void updateContext(IGISContext gisContext); public IGISContext getCurrentContext(); }
Context service is implemented as a normal spring bean, and made available as usual on web side. However, we need to provide a mechanism to expose this service interface to our Applet client. We utilized Spring’s HttpInvokerServiceExporter to expose this service bean to the outside world.
<bean name="/gisContextService.spring" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service"> <ref bean="gisContextService"/> </property> <property name="serviceInterface"> <value>tbs.ortak.cbs.servis.IGISContextService</value> </property> </bean>
There appeared also one more problem on this Applet view side. As you may know, that exposed service is accessed in a stateless manner, that is, each time when a client invokes methods, requests are executed in a different HTTP session. In our case, however, we need our client Applet to make all of its service method invocations over the same HTTP session, because those invocations will act on the same context instance each time. We solved this issue, by passing session id information as an applet initialization parameter, and constructed HTTP invoker client proxy with using this session id.
<OBJECT ID="fviewer" name="fviewer" classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" width="900" height="600" align="CENTER"> <PARAM NAME=CODE VALUE="tbs.ortak.cbs.fviewer.FViewerApplet"> <PARAM NAME=CODEBASE VALUE="http://localhost:8080/GISTestWebApp/fviewerlib"> <PARAM NAME="type" VALUE="$"> <PARAM NAME="scriptable" VALUE="false"> <PARAM NAME="archive" VALUE="tbs.cbs.fviewer.jar"> <PARAM NAME="sessionId" VALUE="<%= session.getId() %>"> <PARAM NAME="serviceName" VALUE="gisContextService.spring"> <PARAM NAME="serviceUrlBase" VALUE="http://localhost:8080/CbsTestWebApp/"> </OBJECT>
private void createGISContextService() { HttpInvokerProxyFactoryBean factoryBean = getHttpInvokerProxyFactoryBean(); try { gisContextService = (IGISContextService)factoryBean.getObject(); } catch (MalformedURLException e) { throw new RuntimeException(e); } } private HttpInvokerProxyFactoryBean getHttpInvokerProxyFactoryBean() throws MalformedURLException { HttpInvokerProxyFactoryBean factoryBean = new HttpInvokerProxyFactoryBean(); String serviceUrl = getServiceUrl(); factoryBean.setServiceUrl(serviceUrl); factoryBean.setServiceInterface(IGISContextService.class); factoryBean.afterPropertiesSet(); return factoryBean; } private String getServiceUrl() { String url = paramServiceUrlBase + paramServiceName; url += (paramSessionId != null && paramSessionId.trim().length() > 0) ? (";jsessionid=" + paramSessionId):""; return url; }
The third, problem was to keep context alive across several web requests, and we achieved this with a Filter similar to Acegi Security Framework’s HttpSessionContextIntegrationFilter mechanism. When a request arrives, we take context from HTTP session, and put it into a ThreadLocal variable, and make it available to our context service bean, and later while response returns back, we take this context from ThreadLocal variable and put it back to HTTP session.
public class ThreadLocalGISContextHolder implements IGISContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal(); private static final IGISContextHolder instance = new ThreadLocalGISContextHolder(); private ThreadLocalGISContextHolder() {} public static IGISContextHolder getInstance() { return instance; } public IGISContext getContext() { Object obj = contextHolder.get(); return obj != null ? (IGISContext)contextHolder.get():new NullGISContext(); } public void setContext(IGISContext gisContext) { contextHolder.set(gisContext); } }
In summary, this server side context sharing mechanism enabled us to easily implement our scenarios, which need to collect user input from both sides, which are from different clients – one is web page, and the other is Applet client – and share resulting data between them.