For a while I am experimenting with flex. I wrote quite some posts about flex and security and I started writing about the Springframework BlazeDS Integration project. One thing that I do not really like about the configuration is the way to configure the remoting. All the hassle with the service-config.xml that needs to be available on the client as well as on the server. Not really nice. Using the maven way of creating a jar with these config files and unzipping this jar into the web server project as well as the client flex project is a way. Still, not ideal when developing in your ide when you want to add a new remote service.

What is the arternative? On the serverside, the mentioned spring project is doing a good job. You do not have to configure all endpoints. But you still need something on the client.

This post talks about a mechanism to enable you to loose all this configuration on the client

An overview

So what am I planning to do. Using the Properties object from the Spring-ActionScript project I will load a property file from the server. This property file will contain the parameters required by the remote objects to connect to an endpoint. The property files will not be on the server, the request will be captured by the Dispatcher servlet from spring. Special controller classes will create the properties requested by the client and a view class creates a stream to the client. That way the client does not know the requested file is actually a dynamic resource.

Does that sound complicated? Well, read on and I’ll explain it step by step.

The server side

As mentioned in the introduction, we use the special spring BlazeDS integration project on the server. I am not going to discuss the configuration anymore. Read my previous post Wow springframework enters the actionscript and flex domain to learn how to do that. For this post I’ll focus on the differences.

So what do we need? We need the server to respond to the url:

http://localhost:8080/books-web/config.properties

with the result:

host=localhost
port=8080
context-root=books-web

And of course the result should be different when running on another server.

web.xml

We add a servlet mapping for a *.properties

<servlet-mapping>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <url-pattern>*.properties</url-pattern>
</servlet-mapping>

spring-web.xml

We need to add a url mapping, and we have to add a mapping for the config.properties request. Now the simple url mapping looks like this.

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <value>
            /config.properties=configPropertyController
            /*=mySpringManagedMessageBroker
        </value>
    </property>
</bean>

Next step is to configure the controller and a default view resolver. The view resolver uses a resource bundle to connect the views and the classes that generate content.

<bean id="configPropertyController" class="nl.gridshore.samples.books.web.controller.ConfigPropertyController"/>
<bean id="defaultViewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
    <property name="order" value="1"/>
</bean>

The last thing to mention for the spring configuration took me a moment to figure out. Usually when creating a spring application, by default a HandlerAdapter is registered. Since the BlazeDS integration registers it’s own HandlerAdapter, the MessageBrokerHandlerAdapter, we need to register an additional HandlerAdapter to take care of our controller. That is why I added the following line to the spring config.

<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

PropertyController

The classes I added are pretty straightforward. I created a super class called PropertyController. This class implements the Controller interface. It is an abstract class that calls the method to create a Map<String,String>. This map is stored in the model and we move on to the view with a name propertiesView.

public abstract class PropertyController implements Controller {
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        Map<String, String> exposedParams = createExposedParamsMap(request);
        return new ModelAndView("propertiesView", "exposedParams", exposedParams);
    }

    abstract protected Map<String, String> createExposedParamsMap(HttpServletRequest request);
}

ConfigPropertyController

For our case, we mapped the config.properties to the class ConfigPropertyController. The implementation makes use of the provided HttpServletRequest to read information about the server.

public class ConfigPropertyController extends PropertyController {
    protected Map<String, String> createExposedParamsMap(HttpServletRequest request) {
        Map<String, String> exposedParams = new HashMap<String, String>();
        exposedParams.put("host", request.getServerName());
        exposedParams.put("port", String.valueOf(request.getServerPort()));

        // The context root path contains a prefix '/', we have to take that of.
        String contextRoot = request.getContextPath();
        contextRoot = contextRoot.substring(1);
        exposedParams.put("context-root", contextRoot);

        return exposedParams;
    }
}

The last piece of the puzzle is the PropertyView class. This class writes out the file to the client with one of the provided properties per line. Exactly like you’d expect a property file to be.

public class PropertyView extends AbstractView {
    public PropertyView() {
        setContentType("text/plain");
    }

    protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Map<String,String> exposedParams = (Map<String,String>)model.get("exposedParams");

        Set<Map.Entry<String,String>> entries = exposedParams.entrySet();

        response.setContentType(getContentType());
        for(Map.Entry<String,String> entry : entries) {
            StringBuilder lineBuilder = new StringBuilder();
            lineBuilder.append(entry.getKey());
            lineBuilder.append("=");
            lineBuilder.append(entry.getValue());
            response.getWriter().println(lineBuilder.toString());
        }
    }
}

Now you can start up the server and test the implementation. Than we continue with the flex client

The Client

On the client side I use Mate, I do not think this changes the implementation of this strategy a lot. Another thing you’ll need is a copy of the spring-ActionScript project. Sorry no maven yet. Get a copy from the site and build it.

http://forum.springsource.org/showthread.php?t=66193

You’ll need this library for easy access to a nice way of loading property files from the server. The next block of code shows some functions that reside in you main flex application file.

[Bindable]
public var properties:Config = new Config();
private var loadedProps:Properties = new Properties();

public function initApp():void {
    loadedProps.addEventListener(Event.COMPLETE, onLoaderComplete);
    loadedProps.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
    loadedProps.load("config.properties");
}

private function onLoaderComplete(event:Event):void {
    properties.host = loadedProps.getProperty("host");
    properties.port = loadedProps.getProperty("port");
    properties.webcontext = loadedProps.getProperty("context-root");
}

First we initiate a load of the file we used in the server side part called config.properties. Responding to the complete event, we set the properties of the Config object using the loaded properties from the server. Next up we initiate the Mate event maps. Notice that we add the properties object to this map under the property props.

<map:MainEventMap props="{properties}"/>

In the EventMap we have to add the property called props of type Config. Using this object we can configure the remote obejcts like this.

    <mx:RemoteObject id="booksService"
                     endpoint="http://{props.host}:{props.port}/{props.webcontext}/messagebroker/amf"
                     destination="remoteBookManager"/>

That is about it, now the client knows how to obtain the server config data and with this information it can access the endpoints.

If you want to have a look at the code or try it yourself, have a look at the following project in google code.

http://code.google.com/p/gridshore/source/browse/#svn/trunk/books-overview

Look for the books-flex-mate module to check the current progress of the new implementation. It is by far from finished. As of now you can only login using user/user and logout again.

Hope this can help you program your flex better, or at least have some inspiration to improve your (or mine) code. Comments are welcome

Flex remoting without configuring the client
Tagged on:             

5 thoughts on “Flex remoting without configuring the client

  • May 19, 2009 at 7:28 am
    Permalink

    Hi again,

    in some cases the context path might be not available. This causes an exception in the ConfigPropertyController. To prevent this use following code:


    public class ConfigPropertyController extends PropertyController {

    protected Map createExposedParamsMap(HttpServletRequest request) {
    Map exposedParams = new HashMap();
    exposedParams.put(“host”, request.getServerName());
    exposedParams.put(“port”, String.valueOf(request.getServerPort()));

    // The context root path contains a prefix ‘/’, we have to take that of.
    String contextRoot = request.getContextPath();
    if (contextRoot == null || contextRoot.isEmpty()) {
    contextRoot = “”;
    } else {
    contextRoot = contextRoot.substring(1);
    }
    exposedParams.put(“context-root”, contextRoot);

    return exposedParams;
    }
    }

  • April 21, 2009 at 3:08 pm
    Permalink

    I am sorry, this exception (though the missing file name is called config.properties) has NOTHING to do with your example…unfortunately there was another (missing) config.properties file in the project 😮

  • April 21, 2009 at 10:45 am
    Permalink

    Hi jettro,

    I think I did the same like you, but when starting the server following exception occurs:


    org.springframework.beans.factory.BeanInitializationException: Could not load properties; nested exception is java.io.FileNotFoundException: class path resource [config.properties] cannot be opened because it does not exist
    at org.springframework.beans.factory.config.PropertyResourceConfigurer.postProcessBeanFactory(PropertyResourceConfigurer.java:78)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:553)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:527)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:362)
    at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:255)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:199)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:45)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3827)
    at org.apache.catalina.core.StandardContext.start(StandardContext.java:4334)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:791)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:771)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:525)
    at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:825)
    at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:714)
    at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:490)
    at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1138)
    at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:311)
    at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1053)
    at org.apache.catalina.core.StandardHost.start(StandardHost.java:719)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
    at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443)
    at org.apache.catalina.core.StandardService.start(StandardService.java:516)
    at org.apache.catalina.core.StandardServer.start(StandardServer.java:710)
    at org.apache.catalina.startup.Catalina.start(Catalina.java:566)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:288)
    at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:413)
    Caused by: java.io.FileNotFoundException: class path resource [config.properties] cannot be opened because it does not exist
    at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:143)
    at org.springframework.core.io.support.PropertiesLoaderSupport.loadProperties(PropertiesLoaderSupport.java:182)
    at org.springframework.core.io.support.PropertiesLoaderSupport.mergeProperties(PropertiesLoaderSupport.java:161)
    at org.springframework.beans.factory.config.PropertyResourceConfigurer.postProcessBeanFactory(PropertyResourceConfigurer.java:69)
    … 30 more

    Any idea? Thanks!

  • February 1, 2009 at 3:44 pm
    Permalink

    Thanks for the code. I was looking for a way to load server-side properties from Mate. I have to ask this though : What happens if the remote object is called before the properties file has finished loading ? Actually is there a risk of that happening ? If yes, how would you prevent that ? Thank you.

    • February 1, 2009 at 10:28 pm
      Permalink

      Of course that is a risk, I make sure that I only start my application when I have the properties. Using events this is not really hard.

Comments are closed.