2. Spring Services
Introduction
Granite Data Services supports out-of-box Spring framework integration. You can easily power your Java destinations with all Spring features including Inversion of Control/Dependency Injection, AOP interceptors, transaction management, etc.
GDS/Spring also fully supports Spring Security.
For a basic sample with GraniteDS/Spring/JPA entites working together, you can have a look at the examples/graniteds_spring folder in the GDS distribution. Download graniteds-***.zip and import examples/graniteds_spring as a new Eclipse project.
Configuration of GraniteDS for Spring can be done in two ways: the generic GDS configuration using services-config.xml and granite-config.xml and the Spring-style way in applicationContext.xml. The latter supports almost all the available GraniteDS functionality, but if you need something very specific you can override it by defining one or both other generic configuration files.
Spring MVC-style Services Configuration
The Spring-style configuration involves a Spring MVC dispatcher servlet and integrates in the standard Spring configuration file.
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/graniteamf/*</url-pattern> </servlet-mapping>
By default we just add an empty WEB-INF/dispatcher-servlet.xml.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd> </beans>
Then just add the GDS configuration in Spring :
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:graniteds="http://www.graniteds.org/config" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.graniteds.org/config http://www.graniteds.org/public/dtd/2.1.0/granite-config-2.1.xsd"> ... <graniteds:flex-filter url-pattern="/*"/> </beans>
The effective url that will be listened to by the AMF processor is the combination of url-pattern in the Spring context and servlet-mapping in web.xml. The configuration described here is suitable in almost all cases.
Note that this configuration can be overriden or completed by the generic configuration described in the next paragraph 'Generic Spring Services Configuration'. In this case, note the configuration that is implicitly built contains the following elements :
- a remoting service named granite-service
- a remoting service factory named spring-factory
- a remoting channel named graniteamf
This configuration automatically enables component scanning, so you can just annotate your Spring services with @RemoteDestination and put an empty META-INF/services-config.properties file in your services jar or folder to tell GraniteDS where to look for services. See last paragraph 'Automatic Configuration of Destinations'.
Spring Security support is automatically enabled when using this kind of configuration and the Spring Security version is automatically detected. However if you have configured many AuthenticationManagers, it will be necessary to instruct GraniteDS which one should be used for authentication by adding the following line to your Spring configuration :
<graniteds:security-service authentication-manager="myAuthenticationManager"/>
Finally remember that as we did not define services-config.xml, you will have to manually initialize the endpoints for your client RemoteObjects :
srv.destination = "personService"; srv.source = "personService"; srv.channelSet = new ChannelSet(); srv.channelSet.addChannel(new AMFChannel("graniteamf", "http://{server.name}:{server.port}/{context.root}/graniteamf/amf"));
Generic Spring Services Configuration
The first step is to configure your Spring destinations with the standard Flex services configuration file :
<?xml version="1.0" encoding="UTF-8"?> <services-config> <services> <service id="granite-service" class="flex.messaging.services.RemotingService" messageTypes="flex.messaging.messages.RemotingMessage"> <destination id="test"> <channels> <channel ref="graniteamf"/> </channels> <properties> <factory>springFactory</factory> <source>springBean</source> </properties> <security> <security-constraint> <auth-method>Custom</auth-method> <roles> <role>ROLE_USER</role> <role>ROLE_ADMIN</role> </roles> </security-constraint> </security> </destination> </service> </services> <factories> <factory id="springFactory" class="org.granite.spring.SpringServiceFactory" /> </factories> <channels> <channel-definition id="graniteamf" class="mx.messaging.channels.AMFChannel"> <endpoint uri="http://{server.name}:{server.port}/{context.root}/graniteamf/amf" class="flex.messaging.endpoints.AMFEndpoint"/> </channel-definition> </channels> </services-config>
Some Comments:
<properties>...</properties>
This block tells GDS to use factory class springFactory, see below, to instantiate current destination.
Tag <source> names a Spring bean, used to represent your destination.
<security>...</security>
This block, which is optional, tells the GDS Spring security service, see below, to check for required roles when accessing the test destination. See the applicationContextSecurity.xml file in examples/graniteds_spring for role configuration.
<factories>...</factories>
This block declares a Spring factory which is used to instantiate your destinations.
You may also customize your granite-config.xml in order to use automated externalization, etc.
Spring Security Service Configuration
To enable Spring Security 3, you have to declare it in granite-config.xml:
<granite-config> ... <!-- ! Use Spring based security service. !--> <security type="org.granite.spring.security.SpringSecurity3Service"/> </granite-config>
To enable Spring security (version 2.x), you must put a declaration in granite-config.xml:
<granite-config> ... <!-- ! Use Spring based security service. !--> <security type="org.granite.messaging.service.security.SpringSecurityService"/> </granite-config>
You may then secure your Flex destinations as shown earlier. Please refer to Acegi (or now Spring Security documentation for specific configuration details.
General Web Configuration (web.xml)
In order to initialize Spring and Acegi security, you must also customize your web.xml as follows:
<web-app version="2.4" ...> ... <!-- Path to Spring config --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/conf/applicationContext.xml, /WEB-INF/conf/applicationContextSecurity.xml </param-value> </context-param> <!-- Spring listener --> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- Spring listener for web-scopes (request, session) --> <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ... </web-app>
For a full example, have a look to the examples/graniteds_spring sample.
Automatic Configuration of Destinations
It is possible to instruct GraniteDS to automatically search for Spring destinations in the classpath by:
- Enabling the automatic scanner :
<granite-config scan="true"> </granite-config>
- Adding a META-INF/services-config.properties file in all jars containing Spring services
- Annotating the Spring service with org.granite.messaging.service.annotations.RemoteDestination
@Service("personService") @RemoteDestination(id="personService", source="personService", securityRoles={"user","admin"}) public class PersonServiceBean implements PersonService { ... }
The annotation supports the following attributes:
- id is mandatory and is the destination name
- source is mandatory and should be the name of the Spring component
- service is optional if there is only one service for RemotingMessage defined in services-config.xml. Otherwise this should be the name of the service.
- channel is optional if there is only one channel defined in services-config.xml. Otherwise this should be the id of the target channel.
- channels may be used instead of channel to define a failover channel.
- factory is optional if there is only one factory in services-config.xml. Otherwise this should be the factory id.
- securityRoles is an array of role names for securing the destination.
As shown below, the service, factory and channel sections are still required in your services-config.xml file, but the service part is shortened by removing all destination configurations.
This requires a little more work on the Flex client because of the absence of destination definitions in services-config.
You then have to create your RemoteObjects with one of the following ways:
srv.endpoint = ServerConfig.getChannel("graniteamf").endpoint;
srv.destination = "personService";
srv.source = "personService";
srv.channelSet = new ChannelSet();
srv.channelSet.addChannel(ServerConfig.getChannel("graniteamf"));
srv.destination = "personService";
srv.source = "personService";
srv.channelSet = new ChannelSet();
srv.channelSet.addChannel(ServerConfig.getChannel("graniteamf"));
