Adobe and Spring made a major announcement the end of last year that they where going to collaborate on a joint project called Spring BlazeDS Integration. The goal was to allow the Spring development model to be used for creating Rich Internet Applications (RIA's) with Adobe Flex, BlazeDS, Spring and Java. This would allow Spring managed services to be exposed through BlazeDS without the overhead of extra configuration files. The primary benefit is that it brings Spring ease of use to creating applications with Flex, BlazeDS and Java.
In this article I look at what the Spring BlazeDS Integration project changes and show a sample of how it works. First looking at how this changes the way applications are wired and how to easily convert an existing Spring project to use this new integration project. We finish up looking at the additional features and benefits of this project.
The sample application that is used through out is simple soda service that provides basic accounting information. The data model it uses is a SodaAccount that represents the client account information.
Developing Rich Internet Applications the Spring Way
When Spring was released it dramatically changed the way Java Server Side Development was done. It emphasized the use of plain old java objects (POJOs) wired together using Dependency Injection. This simplified development and testing of applications.
The core configuration of Spring is done with JavaBeans. With Beans, any Java classes can exposed as a service. For example, the following declares the Soda Service as a Spring Bean:
<!-- Implementation of soda bean--> <bean id="sodaBean" class="com.gorillalogic.sodaBank.SodaService" init-method="initSodaAccounts"> <property name="numAccounts" value="1000"/> </bean>
For exporting these beans as Remote Services for Flex clients, the integration project uses Spring Web MVC. Spring Web MVC uses DispatchServlet as a central dispatcher for any type of HTTP request or HTTP-based remote services. The Dispatch Servlet is configured using the same JavaBeans configuration mechanism to forward requests to a handler adapter for processing.
Previously, a BlazeDS project would use the MessageBrokerServlet to route requests to the BlazeDS Message Broker. With Spring BlazeDS this is replaced with the Spring Web MVC DispatchServlet. The DispatchServlet is then configured to forward HTTP message to the MessageBrokerHandlerAdapter. This adapter is configured as a Spring factory bean. It creates a local a BlazeDS Message Broker instance within the Spring web application context. It is then used to export Spring beans as remote service that can be called for directly from Flex clients.
This approach to configuring the BlazeDS Message Broker allows for a tighter integration with Spring projects. It also reduces the configuration need for exporting Spring Beans as remote services. For example previously you had to declare a separate entry in messaging.xml to export your Java services. Now you simply wire a remoting export bean in the same file that you declare your Spring Beans.
Spring BlazeDS Integration does use some of the standard BlazeDS XML configuration files for configuring the messaging infastructure. This includes things such as channel dfinitions.
The next release of the project is adding integration with Spring Security. The initial implementation secures the BlazeDS endpoints using a pointcut advisor. Pointcut advisor are part of the Spring Aspect Oriented Programming (AOP) support.
Setting up a new Spring BlazeDS Integration Project - Server Side
Whether you are setting up a new project or adding the support to an existing project, the steps are fairly similiar. The first step is to add all of the needed jar files to your library directory. You can download them off the Spring Source website (http://www.springsource.org/spring-flex) or use the sample project library.
For this sample we will take a simple Soda Service project and modify it to use the Spring BlazeDS project. The first change is to the web.xml. In this file all references to the BlazeDS MessageBrokerServlet are removed. Instead a reference to the Spring DispatchServlet is used:
<servlet> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/config/web-application-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> <url-pattern>/gorilla/*</url-pattern> </servlet-mapping>
This routes all requests to the Dispatcher Servlet using the standard servlet mapping pattern. It also specifies the context configuration to point to web-application-config.xml.
The standard BlazeDS files are located in WEB-INF/flex. The primary file is services-config.xml that defines the channels, logging and other system configuration. The one change in this file is the URL of the standard AMF channel is changed to route requests through the Dispatcher Servlet:
<channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel"> <endpoint url="http://{server.name}:{server.port}/{context.root}/ gorilla/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/>
The web-application-config.xml file is the main configuration file. In fact once the other files are set-up this will often be the only file that you need to modify. In web-application-config.xml the MessageBrokerHandlerAdapter is declared so HTTP messges are routed to the Spring managed Message Broker.
<bean class="org.springframework.flex.messaging.servlet.MessageBrokerHandlerAdapter"/>
The other part of the framework that is declared in the file is the MessageBrokerFactoryBean. This bean maps the URL we want to handle, which is all requests sent to the Dispatch Servlet:
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <value> /*=mySpringManagedMessageBroker </value> </property> </bean> <bean id="mySpringManagedMessageBroker" class="org.springframework.flex.messaging.MessageBrokerFactoryBean"/>
Because the DispatcherServlet is listening on /gorilla, this configuration will pass all requests sent to the /gorilla URL to the Spring-managed MessageBroker. Then because message broker uses the channel configuration out of the WEB-INF/flex/services-config.xml, messages sent to /gorilla/messagebroker will be routed to the beans we declare as services.
So that the message broker nows about our beans, the next part of the configuration is the declaration of the applications Spring Beans and exporting them as remote services. In our example we declare the SodaService as a sodaBean:
<bean id="sodaBean" class="com.gorillalogic.sodaBank.SodaService" init-method="initSodaAccounts"> <property name="numAccounts" value="1000"/> </bean>/
Then the sodaBean is exposed for BlazeDS remoting:
<bean id="sodaService" class="org.springframework.flex.messaging.remoting.FlexRemotingServiceExporter"> <property name="messageBroker" ref="mySpringManagedMessageBroker"/> <property name="service" ref="sodaBean"/> </bean>
This references the previous Message Broker bean to export the sodaBean. By default all of the methods on the class are exposed as a remote service. The FlexRemotingServiceExporter has a number of options that can configure the service. For example the methods that are exposed can be set using includeMethods and excludeMethods.
Setting up new Spring BlazeDS Integration Project - Client Side
The client side code for connecting to the server uses the standard Flex RemoteObjects. For the sample application we declare the following RemoteObject:
<mx:RemoteObject id="remoteObject" destination="sodaService" result="resultHandler(event);" fault="faultHandler(event);" channelSet="{sodaChannels}"/> <mx:ChannelSet id="sodaChannels"> <mx:AMFChannel uri="/gorilla/messagebroker/amf"/> </mx:ChannelSet>
This remote object allows the Flex Client to call the remote Java Server. To have the client know what channel to send the call on there are two options. The first is to compile the client against the server configuration file, services-config.xml. This is generally not an ideal solution, because it tightly couples the client to the server. Instead the channel is configured in the client with a channel set.
The call to the RemoteObject is similar to calling the object locally, the difference is the results come back asynchronously. For this reason a resultHandler is declared that is called when the results come back from the server. In the sample the call to the server is made as follows:
remoteObject.getSodaModel();
The results come back in a ResultEvent. These results are cast to our sodaModel:
sodaModel = event.result as SodaModel;
Securing the Remote Services - Server Side
To secure the communication with the server the Spring BlazeDS Integration project uses a custom authentication and authorization process. The process integrates Spring Security with the BlazeDS security process. (Note this uses code that at the time of writing was only available in the latest code checked out from SVN. The snapshot of the code we used for these samples is included as jar file).
The first part of configuring the security on the server is to define the security context. This defines the username, password and associated roles for the users. In this simple example we just define the users in the file. An enterprise project would probably want to authenticate against a database or using single-sign on.
To declare the users in the system we use a separate file, called security-context.xml. This file needs to be added to the Dispatcher Servlet context configuration so that it is loaded at startup. To do this we add the file to the configuration in web.xml to point to our file:
<servlet> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</ servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/config/security-context.xml /WEB-INF/config/web-application-config.xml </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
Then in the security-context file we declare the system users:
<authentication-provider> <user-service> <user name="ryan" password="monkey" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="alex" password="chimp" authorities="ROLE_USER" /> </user-service> </authentication-provider>
The second part of setting up the security on the server is to configure the message broker with the custom security configuration. To allow the Spring-managed message broker to be configured with additional services and/or security it allows additional configuration processors to be set on the MessageBrokerFactoryBean. The processors implement two methods, processBeforeStartup and processAfterStartup, which allow configuration to be set before or after startup of the message broker.
To configure the security of the message broker two processes are set. The first is the login command which provides authentication and authorization and the second is a security configuration processor, secures individual channels and URL's.
LoginCommand is the name of the interface in BlazeDS used to define the custom authentication and authorization process. The bean, SpringSecurityLoginCommand then integrates Spring Security with BlazeDS security, passing BlazeDS calls to authentication and authorization to the Spring managed security context. The following declares an instance of this bean, referencing the previously defined security context:
<bean id="loginCommand" class="org.springframework.flex.messaging.security.SpringSecurityLoginCommand"> <constructor-arg ref="_authenticationManager"/> </bean>
The second process, a security configuration processor, is defined as a pointcut advisor that defines how individual channels and URL's are secured. A pointcut advisor is part of Spring AOP (aspect-oriented programming) that defines the advice that is called prior to the invocation of certain methods. What this essentially does is filter the calls to the remote services and blocks unauthorized calls.
Internally BlazeDS uses AMF Filters to perform pre and post processing on message invocations. These filters work similar to servlet filters and follow the standard pipe-and-filter design pattern. This allows an individual filter to stop processing of a message. The security process advices the AMF Filters of the channels to add Spring managed security.
To define the security processor, first we add two additional channels to the WEB-INF/flex/services-config.xml file:
<channel-definition id="my-protected-amf" class="mx.messaging.channels.AMFChannel"> <endpoint url="http://{server.name}:{server.port}/{context.root}/ gorilla/protected/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/> </channel-definition> <channel-definition id="my-protected-by-id-amf" class="mx.messaging.channels.AMFChannel"> <endpoint url="http://{server.name}:{server.port}/{context.root}/ gorilla/protected2/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/> </channel-definition>
Next we define an endpoint source. This configures what endpoints or channels we want to secure and what roles can access them. For the sample we define a single role of user. We then configure one URL and one endpoint to secure:
<bean id="configAttribute" class="org.springframework.security.ConfigAttributeDefinition"> <constructor-arg type="java.lang.String" value="ROLE_USER"/> </bean> <bean id="endpointSource" class="org.springframework.flex.messaging.security.EndpointDefinitionSource"> <constructor-arg> <bean class="org.springframework.security.util.AntUrlPathMatcher"/> </constructor-arg> <constructor-arg> <map> <entry> <key> <bean class="org.springframework.security.intercept.web.RequestKey"> <constructor-arg value="**/protected/ messagebroker/**"/> </bean> </key> <ref bean="configAttribute"/> </entry> </map> </constructor-arg> <constructor-arg> <map> <entry> <key> <value>my-protected-by-id-amf</value> </key> <ref bean="configAttribute"/> </entry> </map> </constructor-arg> </bean>
This secures any channels which use the URL matching **/protected/messagebroker/**. In the sample this includes the my-protected-amf channel which listens on the /gorilla/protected/messagebroker/amf and the my-protected-by-id-amf channel.
Next we define the endpoint interceptor and exception translator to tie all of this configuration together:
<bean id="endpointInterceptor" class="org.springframework.flex.messaging.security.EndpointServiceMessagePointcutAdvisor"> <constructor-arg> <bean class="org.springframework.flex.messaging.security.EndpointInterceptor"> <property name="accessDecisionManager" ref="_accessManager"/> <property name="authenticationManager" ref="_authenticationManager"/> <property name="objectDefinitionSource" ref="endpointSource"/> </bean> </constructor-arg> </bean> <bean id="exceptionTranslator" class="org.springframework.flex.messaging.security.EndpointServiceMessagePointcutAdvisor"> <constructor-arg> <bean class="org.springframework.flex.messaging.security.SecurityExceptionTranslationAdvice"/> </constructor-arg> </bean>
This configures the endpoint interceptor to apply the access and authentication managers on the endpoint sources.
And finally we change the definition of the Spring-managed message broker to use these configuration processors:
<bean id="mySpringManagedMessageBroker" class="org.springframework.flex.messaging.MessageBrokerFactoryBean"> <property name="configProcessors"> <set> <ref bean="loginCommand"/> <ref bean="securityConfigProcessor"/> </set> </property> </bean>
This will cause the message broker to secure the channels defined in the endpoint interceptor with the security context we configured in the security-context.xml file. Now when we define our service, we also define the channels we want our service to communicate on.
<bean id="sodaService" class="org.springframework.flex.messaging.remoting.FlexRemotingServiceExporter"> <property name="messageBroker" ref="mySpringManagedMessageBroker"/> <property name="service" ref="sodaBean"/> <property name="channelIds" value="my-protected-amf, my-protected-by-id-amf"/> </bean>
For the soda service we have defined that it can only communicate over secure channels. This will block any calls to the service that are not from authenticated users in the correct role.
Securing the Remote Services - Client Side
Client side configuration of securing remote services is a much simpler process. All of the heavy lifting is done on the server side. On the client we change the remote object definition to include a secure channel:
<mx:RemoteObject id="remoteObject" destination="sodaService" result="resultHandler(event);" fault="faultHandler(event);" channelSet="{sodaChannels}"/> <mx:ChannelSet id="sodaChannels"> <mx:AMFChannel uri="/gorilla/protected/messagebroker/amf"/> </mx:ChannelSet>
Now the remote object has to be authenticated before we can make calls to the soda service. For example, if we did not authenticate and called the soda service to get the Soda Model the client would get the following error:
Received fault: [RPC Fault faultString="An Authentication object was not found in theB SecurityContext" faultCode="Client.Authentication" faultDetail="null"]
To authenticate the client we simply pass login information to the channel set. An oversimplified example would be as follows:
var token:AsyncToken = sodaChannels.login(username.text, password.text); token.addResponder( new AsyncResponder( function(result:ResultEvent, token:Object = null):void{ remoteObject.getSodaModel(numAccounts.text); }, function(result:FaultEvent, token:Object = null):void{ ta.text += "Received fault: " + result.fault + "\n"; } ) );
This takes the login information from a username and password box and authenticates the user. Upon successful authentication the remote service is called, retrieving the Soda Model.
Summary
The Spring BlazeDS Integration project simplifies the development of Rich Internet Applications in Java by using the Spring development model. By providing integration with Spring Beans and Spring Security it simplifies exposing and securing Java classes as Remote Services directly to Flex clients. Overall this project will greatly aid in the development of Enterprise Applications with Flex, BlazeDS and Java.
Future releases of the Integration project will add further integration with Spring. Planned features include integration with further integration with Spring Security and JMS. There is also a planned a custom schema definition for defining the remote services on the server. This will greatly simplify the configuration files.