graniteds.orgCommunity Documentation

Chapter 10. Integration with CDI

10.1. Configuration with Servlet 3
10.2. Default Configuration
10.3. Using the Tide API
10.3.1. Basic remoting with dependency injection
10.3.2. Typesafe Remoting with Dependency Injection
10.3.3. Integration with Conversations
10.3.4. Integration with Events
10.3.5. Security
10.4. Messaging with CDI (Gravity)

The Context and Dependency Injection specification is a powerful new feature of Java EE 6. It integrates on a common programming model all the services provided by Java EE.

GraniteDS provides out-of-the-box integration with CDI via the Tide API. You can remotely call CDI beans, and it fully supports serialization of JPA entities from and to your Flex application, taking care of lazily loaded associations. The support for JPA entity beans is covered in the section JPA and lazy initialization, so this section will only describe how to call CDI components from a Flex application. GraniteDS also integrates with container security for authentication and role-based authorization.

The support for CDI is included in the library granite-cdi.jar, so you always have to include this library in either WEB-INF/lib or lib for an ear packaging.

Note

Only the reference implementation Weld is supported for now because of some inconsistencies in a few parts of the spec (notably conversations). This is the one used in JBoss 6 and GlassFish v3.

To provide a more native experience for CDI developers when used in a Servlet 3 compliant container, the CDI support in GraniteDS can be configured with a simple annotated class. The most important features of GraniteDS can be configured this way, and it is still possible to fall back to the default GraniteDS configuration files services-config.xml and granite-config.xml for unsupported features.

On Servlet 3 compliant containers, GraniteDS can use the new APIs to automatically register its own servlets and filters and thus does not need any particular configuration in web.xml. This automatic setup is triggered when GraniteDS finds a class annotated with @ServerFilter in one of the application archives:



@ServerFilter(configProvider=CDIConfigProvider.class)
public class GraniteConfig {
}  
        

The ConfigProvider class defines suitable default values for the CDI integration. It is possible however to override these values by setting them in the annotation properties :



@ServerFilter(
        tide=true,
        type="cdi",
        factoryClass=CDIServiceFactory.class,
        tideInterfaces={Identity.class}
)
public class GraniteConfig {
}  
        

As for any CDI application, don't forget to add a file WEB-INF/beans.xml, even empty. Note than only the Tide API is currently supported out-of-the-box with CDI (there is no basic service factory for RemoteService).

The @ServerFilter declaration will setup an AMF processor for the specified url pattern, and the tide attribute specifies that you want a Tide-enabled service factory. The default url pattern for remoting /graniteamf/amf.txt and messaging /gravityamf/amf.txt.

Other configurations can be done with @ServerFilter:

When using the ConfigProvider allows Tide to search in the CDI context for some of its configuration elements. For now, it will lookup beans that implement ExceptionConverter, AMF3MessageInterceptor or SecurityService and use the existing beans.

If you don't use the Servlet 3 configuration, you will have to use the standard GraniteDS configuration files instead, and setup these elements manually. You can safely skip this section if you choose Servlet 3 configuration.

Here is a default configuration suitable for most cases:



<granite-config scan="true">
    ...
    
    <tide-components>
        <tide-component annotated-with="org.granite.messaging.service.annotations.RemoteDestination"/>
        <tide-component annotated-with="org.granite.tide.annotations.TideEnabled"/>
    </tide-components>
    
</granite-config>    
            


<services-config>

    <services>
        <service id="granite-service"
            class="flex.messaging.services.RemotingService"
            messageTypes="flex.messaging.messages.RemotingMessage">
            <!--
             ! Use "tideCdiFactory" and "my-graniteamf" for "cdi" destination (see below).
             ! The destination must be "cdi" when using Tide with default configuration.
             !-->
            <destination id="cdi">
                <channels>
                    <channel ref="my-graniteamf"/>
                </channels>
                <properties>
                    <factory>tideCdiFactory</factory>
                </properties>
            </destination>
        </service>
    </services>

    <!--
     ! Declare tideCdiFactory service factory.
     !-->
    <factories>
        <factory id="tideCdiFactory" class="org.granite.tide.cdi.CdiServiceFactory"/>
    </factories>

    <!--
     ! Declare my-graniteamf channel.
     !-->
    <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>    
       

The destination named cdi will be the one and only destination required for all CDI destinations.

Most of what is described in the Tide Remoting section applies for CDI, however GraniteDS also provides a much improved integration with CDI when using the Tide client API.

When using CDI, the only difference on the client is that you have to use the Cdi singleton. Here is a simple example of remoting with an injected client proxy for an CDI bean:



<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="Cdi.getInstance().initApplication()">
    <mx:Script>
        import org.granite.tide.cdi.Cdi;
        import org.granite.tide.events.TideResultEvent;
        import org.granite.tide.events.TideFaultEvent;
        
        [In]
        public var helloService:Component;
        
        private function hello(name:String):void {
            helloService.hello(name, resultHandler, faultHandler);
        }
        
        private function resultHandler(event:TideResultEvent):void {
            outputMessage.text = event.result as String;
        }                       
        
        private function faultHandler(event:TideFaultEvent):void {
            // Handle fault
        }
    </mx:Script>
    
    <!-- Provide input data for calling the service. --> 
    <mx:TextInput id="inputName"/>
    
    <!-- Call the web service, use the text in a TextInput control as input data.--> 
    <mx:Button click="hello(inputName.text)"/>
    
    <!-- Result message. --> 
    <mx:Label id="outputMessage"/>
</mx:Application>
            

This is almost identical to the standard Tide API described in the Tide remoting section, and all other methods apply for CDI.

You can benefit from the capability of the Gas3 code generator (see here) to generate a strongly typed ActionScript 3 client proxy from the CDI bean interface when it is annotated with @RemoteDestination. In this case, you can inject a typesafe reference to your service and get better compile time error checking and auto completion in your IDE:



<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="Cdi.getInstance().initApplication()">
    <mx:Script>
        import org.granite.tide.cdi.Cdi;
        import org.granite.tide.events.TideResultEvent;
        import org.granite.tide.events.TideFaultEvent;
        import com.myapp.service.HelloService;
        
        [In]
        public var helloService:HelloService;
        
        private function hello(name:String):void {
            helloService.hello(name, resultHandler, faultHandler);
        }
        ...
    </mx:Script>
    
    ...
</mx:Application>
            

It is possible to benefit from even more type safety by using the annotation [Inject] instead of In. When using this annotation, the full class name is used to find the target bean in the CDI context instead of the bean name.



<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="Cdi.getInstance().initApplication()">
    <mx:Script>
        import org.granite.tide.cdi.Cdi;
        import org.granite.tide.events.TideResultEvent;
        import org.granite.tide.events.TideFaultEvent;
        import com.myapp.service.HelloService;
        
        [Inject]
        public var myService:HelloService;
        
        private function hello(name:String):void {
            myService.hello(name, resultHandler, faultHandler);
        }
        ...
    </mx:Script>
    
    ...
</mx:Application>
            

This typesafe mode allows to better detect API inconsistencies between the Flex application and the Java services, because the Flex compiler will immediately warn you when a server method signature has changed (and Gas3 has regenerated the client proxy).

Until now, all client-server communications have been done through the global Tide client context. Tide supports secondary client contexts which represent particular server conversations.

When a remote component call triggers the beginning of a new conversation, the context referenced by the TideResultEvent is a new context object corresponding to this conversation. Of course many such contexts can exist simultaneously on the Flex client, and correspond to different server conversations.

Variables having less than conversation scope are managed in the corresponding context. Session scoped variables and components are always managed in the global context.



@Stateful
public class HotelBookingAction implements HotelBooking {
    ...
    @Inject
    private Conversation conversation;
    
    public void selectHotel(Hotel selectedHotel) {
        conversation.begin();
        hotel = em.merge(selectedHotel);
    }
    ...
}
           
public function selectHotel(hotel:Hotel):void {
(1) tideContext.hotelBooking.selectHotel(hotel, selectHotelResult);
}

private function selectHotelResult(event:TideResultEvent):void {
(2) var localContext:Context = event.context as Context;
    var hotel:Hotel = localContext.hotel;
}
           

All following operations must be then done through the localContext to be executed in the correct server conversation context. That means mainly that this context object has to be stored somewhere in the application, for example in the MXML corresponding to a particular wizard component. Optionally, it is also possible to store only the conversationId, and retrieve the context object by:

var localContext:Context = Cdi.getInstance().getCdiContext(conversationId)
           

When the conversation ends, the context object returned in the result events remains the local conversation context, to allow the Flex client to get the last call resulting context variables. It is deleted just before the next remote component call on the global context.

GraniteDS provides a client-side component named identity that ensures the integration between the client RemoteObject credentials and the server-side container security. It additionally includes an easy-to-use API to define runtime authorization checks on the Flex UI.

The CDI identity component (of class org.granite.tide.cdi.Identity) predictably provides two methods login() and logout() that can be used as any Tide remote call:

private var tideContext:Context = Cdi.getInstance().getCdiContext();

public function login(username:String, password:String):void {
    tideContext.identity.login(username, password, loginResult, loginFault);
}

private function loginResult(event:TideResultEvent):void {
    Alert.show(event.context.identity.loggedIn);
}

private function loginFault(event:TideFaultEvent):void {
    Alert.show(event.fault);
}

public function logout():void {
    tideContext.identity.logout();
}
            

Or with dependency injection:

[In]
public var identity:Identity;
            
public function login(username:String, password:String):void {
    identity.login(username, password, loginResult, loginFault);
}

private function loginResult(event:TideResultEvent):void {
    Alert.show(event.context.identity.loggedIn);
}

private function loginFault(event:TideFaultEvent):void {
    Alert.show(event.fault);
}

public function logout():void {
    identity.logout();
}
            

The identity component also exposes the bindable property loggedIn that represents the current authentication state. As it is bindable, it can be used to choose between different views, for example to switch between a login form and the application view with a Flex ViewStack component:



<mx:ViewStack id="main" selectedIndex="{identity.loggedIn ? 1 : 0}">
    <views:LoginView id="loginView"/>
    <views:MainView id="mainView"/>
</mx:ViewStack>
            

Finally the identity component is integrated with server-side role-based security and can be used to get information or show/hide UI depending on the user access rights:



<mx:Button id="deleteButton" 
    label="Delete"
    enabled="{identity.hasRole('admin')}"
    click="myService.deleteEntity(myEntity)"/>

            

With this declaration, this button labeled Delete will be enabled only if the user has the role admin. Another possibility is to completely hide the button with the properties visible and includeInLayout, or any other property relevant for the UI component.

This can also be used as any remote class with result and fault handlers:

 public function checkRole(role:String):void {
    identity.hasRole(role, checkRoleResult, checkRoleFault);
 }
 
 private function checkRoleResult(event:TideResultEvent, role:String):void {
    if (role == 'admin') {
        if (event.result)
            trace("User has admin role");
        else
            trace("User does not have admin role");
    }
 }
            

You can notice that the result and fault handlers have a second argument so you can use the same handler for many access check calls.

It is important to note that identity caches the user access rights so only the first call to hasRole() will be remote. If the user rights are changed on the server, or if you want to enforce security more than once per user session, you can clear the security cache manually with identity.clearSecurityCache(), for example periodically in a Timer.

As with EJB 3 and when using a servlet 3 compliant container, it is possible to configure the three kinds of Gravity topics in the configuration class annotated with @ServerFilter. You can simply add variables to your configuration class annotated with @MessagingDestination, @JmsTopicDestination or @ActiveMQTopicDestination, the name of the variable will be used as destination id.

Simple Topic:



@FlexFilter()
public class MyConfig {
    @MessagingDestination(noLocal=true, sessionSelector=true)
    AbstractMessagingDestination myTopic;
}
       

This declaration supports the properties no-local and session-selector (see the Messaging Configuration section).

You can also define a secure destination by specifying a list of roles required to access the topic:



@MessagingDestination(noLocal=true, sessionSelector=true, roles={ "admin", "user" })
AbstractMessagingDestination myTopic;
       

JMS Topic:



@JMSTopicDestination(noLocal=true, 
    sessionSelector=true, 
    connectionFactory="ConnectionFactory", 
    topicJndiName="topic/myTopic",
    transactedSessions=true,
    acknowledgeMode="AUTO_ACKNOWLEDGE", 
    roles={ "admin", "user" })
AbstractMessagingDestination myTopic;
       

This declaration supports all properties of the default JMS declaration in services-config.xml except for non local initial context environments (see the JMS Integration section).

ActiveMQ Topic:



@ActiveMQTopicDestination(noLocal=true, 
    sessionSelector=true, 
    connectionFactory="ConnectionFactory", 
    topicJndiName="topic/myTopic",
    transactedSessions=true,
    acknowledgeMode="AUTO_ACKNOWLEDGE",
    brokerUrl="vm://localhost",
    createBroker=true,
    waitForStart=true,
    durable=true,
    fileStoreRoot="/opt/activemq/data",
    roles={ "admin", "user" })
AbstractMessagingDestination myTopic;
       

This declaration supports all properties of the default ActiveMQ declaration in services-config.xml except for non-local initial context environments (see the ActiveMQ Integration section).

Finally note that the Gravity singleton that is needed to push messages from the server (see here) is available as a CDI bean and can be injected in any component :



@Inject
private Gravity gravity;