Exposer ses composants Fractal en Webservice dans Petals ESB #2


Dans un précédent article sur l’exposition de composants Fractal en Web service dans Petals ESB, je décrivais la démarche technique à suivre qui comprenait un certain nombre d’étapes (contraignantes), telles que :

  1. Avoir l’obligation de créer un composant spécifique par service à exposer
  2. Avoir l’obligation d’implémenter une interface spécifique (KernelWebService)
  3. Avoir l’obligation de décrire et de binder le service dans les fichiers de description ADL

Disons que cette approche est finalement assez simple mais devient vite lourde lors que l’ajout de services. La solution du jour va supprimer les 3 points cités ci dessus. Comment?

  1. Un composant implémentant une interface annoté en JAXWS doit être la condition suffisante pour être exposé en Web service
  2. Un composant spécifique est en charge d’exposer les composants Fractal qui satisfont la condition 1.

Le composant introduit dans le point 2 ci dessus, que l’on appellera le WSM (‘Web service Manager’) est le composant qui introspecte le composite Fractal dans lequel il est instancié. Il a ainsi la visibilité des composants qui implémentent une interface annotée en JAXWS et peut l’exposer, ici en utilisant Apache CXF.
En allant plus loin, on peut facilement imaginer avoir un WSM de haut niveau qui peut se balader dans l’arbre complet du modèle Fractal et exposer tout les services du bus (ou une partie que l’on juge intéressante par filtrage).

Note pour l’équipe : Ce code est disponible sur la forge de mon projet recherche, disponible sur demande 😉

Extensions de Petals ESB : LifeCycleListener


Petals ESB offre des possibilités d’extensions assez impressionnantes venant du fait que l’on utilise le Framework à composants Fractal (bon c’est pas Spring mais on peut faire des choses intéressantes avec des produits issus de la recherche). Dans cet article je vais m’intéresser à la customisation du démarrage et de l’arrêt de Petals en proposant une extension sympa et ce sans modifier énormément le code du kernel de Petals (Ca peut aussi servir de tutorial Fractal pour les débutants…).
Pourquoi? Parce-que actuellement pour moi Petals ESB est le framework SOA que j’étends pour lui ajouter des fonctionnalités qui ne sont pas fournies par défaut. La fonctionnalité du jour doit me permettre d’attendre que tous les composants par défaut soit démarrés pour démarrer les miens : Je dois installer des composants JBI (ne marchera pas si je laisse Fractal les installer trop tôt), lier des services (ne marchera pas si mes composants ne sont pas installés et si le service de management de Petals n’est pas démarré), lancer des tâches de fond, etc…

l’API du kernel de Petals propose l’interface org.ow2.petals.kernel.api.server.PetalsStateListener :

package org.ow2.petals.kernel.api.server;

/**
 * Listener notified when a Petals container finish to start or to stop.
 */
public interface PetalsStateListener {

    /**
     * Callback method on PEtALS start up
     */
    void onPetalsStarted();

    /**
     * Callback method on PEtALS stop
     *
     * @param success
     *            {@code true} if PEtALS has stopped successfully
     * @param exception
     *            the exception if the stop has failed, {@code null} otherwise
     */
    void onPetalsStopped(boolean success, Exception exception);
}
<pre>

Cette interface n’est malheureusement utilisée que statiquement et une seule fois pour informer le ‘launcher’ que le kernel est bien démarré. On a pourtant org.ow2.petals.kernel.api.server.PetalsServer qui définit la méthode addPetalsStateListener(PetalsStateListener). je vais donc utiliser cette API et modifier simplement l’implémentation de org.ow2.petals.kernel.api.server.PetalsServer pour ajouter des listeners qui seront appelés lors du démarrage et de l’arrêt de Petals.

Pour ce faire deux approches sont possibles :

  1. Rechercher tous les composants Fractal qui implémentent org.ow2.petals.kernel.api.server.PetalsStateListener
  2. Créer un manager qui contient une liste ordonnée de tous les org.ow2.petals.kernel.api.server.PetalsStateListener

On va ici se pencher sur la solution 2 car la solution 1 ne permet d’ordonner facilement les listeners à appeler (ca peut valoir le coup de les appeler dans un sens bien définit!).

1/ Le manager de listeners

Simplement un composant Fractal qui détient la liste des listeners définis. Ces listeners sont définis par configuration dans les fichiers ADL de Petals. Son interface :

package foo.bar.petals.kernel.listener;

import java.util.Set;

import org.ow2.petals.kernel.api.server.PetalsStateListener;

/**
 * @author chamerling - eBM WebSourcing
 *
 */
public interface LifeCycleListenerManager {

    /**
     * The binding prefix of the component which is listening petals state
     */
    static final String PREFIX = "petals-state-listener-";

    /**
     * Get an ordered set of listeners
     *
     * @return
     */
    Set<PetalsStateListener> getListeners();

}

et son implémentation :

package foo.bar.petals.kernel.listener;

import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.objectweb.fractal.fraclet.annotation.annotations.FractalComponent;
import org.objectweb.fractal.fraclet.annotation.annotations.Interface;
import org.objectweb.fractal.fraclet.annotation.annotations.LifeCycle;
import org.objectweb.fractal.fraclet.annotation.annotations.Monolog;
import org.objectweb.fractal.fraclet.annotation.annotations.Provides;
import org.objectweb.fractal.fraclet.annotation.annotations.Requires;
import org.objectweb.fractal.fraclet.annotation.annotations.type.Cardinality;
import org.objectweb.fractal.fraclet.annotation.annotations.type.Contingency;
import org.objectweb.fractal.fraclet.annotation.annotations.type.LifeCycleType;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.petals.kernel.api.server.PetalsStateListener;
import org.ow2.petals.util.LoggingUtil;

/**
 * @author chamerling
 *
 */
@FractalComponent
@Provides(interfaces = { @Interface(name = "service", signature = LifeCycleListenerManager.class) })
public class LifeCycleListenerManagerImpl implements LifeCycleListenerManager {

    @Monolog(name = "logger")
    private Logger logger;

    private LoggingUtil log;

    @Requires(name = PREFIX, signature = PetalsStateListener.class, cardinality = Cardinality.COLLECTION, contingency = Contingency.OPTIONAL)
    private final Map<String, Object> listeners = new Hashtable<String, Object>();

    @LifeCycle(on = LifeCycleType.START)
    protected void start() {
        this.log = new LoggingUtil(this.logger);
        this.log.debug("Starting...");
    }

    @LifeCycle(on = LifeCycleType.STOP)
    protected void stop() {
        this.log.debug("Stopping...");
    }

    /**
     * {@inheritDoc}
     */
    public Set<PetalsStateListener> getListeners() {
        HashSet<PetalsStateListener> result = new HashSet<PetalsStateListener>();
        if (this.listeners != null) {
            Iterator<String> iter = this.listeners.keySet().iterator();
            while (iter.hasNext()) {
                String key = iter.next();
                Object o = this.listeners.get(key);
                if (o instanceof PetalsStateListener) {
                    result.add((PetalsStateListener) o);
                } else {
                    // warning
                    this.log.warning(o + " is not an instance of "
                            + PetalsStateListener.class.getCanonicalName());
                }
            }
        }
        return result;
    }
}

A noter que la Map ‘listeners’ est remplie par Fractal au démarrage de Petals.

2/ Modification de PetalsServerImpl

Dans org.ow2.petals.kernel.server.PetalsServerImpl je rajoute la méthode initListeners() :

   private void initListeners() {
        // get the component which owns the listeners
        Component listenerManagerComponent = FractalHelper.getRecursiveComponentByName(
                this.petalsContentController, Constants.FRACTAL_LISTENERS_MANAGER);
        if (listenerManagerComponent != null) {
            try {
                LifeCycleListenerManager manager = (LifeCycleListenerManager) listenerManagerComponent
                        .getFcInterface("service");

                if (manager != null) {
                    Set<PetalsStateListener> listeners = manager.getListeners();
                    for (PetalsStateListener petalsStateListener : listeners) {
                        this.addPetalsStateListener(petalsStateListener);
                    }
                } else {
                }
            } catch (Exception e) {
            }
        }

    }

Ce code permet de retrouver mon ‘listener manager’ (puisque PetalsServerImpl n’est pas ‘Fractalisé’) et de remplir la liste des listeners. Allez maintenant, on va dire que j’appelle cette méthode d’initialisation juste avant d’appeler les listeners (dans start() de PetalsServerImpl):

    public void start() throws PetalsException {
        // get listeners which have been defined by configuration (Fractal)
        this.initListeners();

        // notify the listener if there is one
        for (PetalsStateListener petalsListener : this.petalsListeners) {
            petalsListener.onPetalsStarted();
        }
    }

3/ Configuration des listeners

Dans les fichiers de définition des composants Fractal, je peux maintenant instancier mes listeners et les ajouter à la liste des listeners gérée par le manager de listeners :

	<!-- lifeCycle Listeners -->
	<component definition="foo.bar.petals.kernel.FooService" name="Foo" />
	<component definition="foo.bar.petals.kernel.BarService" name="Bar" />

	<!-- Listeners manager -->
	<component definition="eu.soa4all.dsb.petals.kernel.listener.LifeCycleListenerManagerImpl" name="LifeCycleListenerManagerImpl" />

	<!-- Fill the map -->
	<binding client="LifeCycleListenerManagerImpl.petals-state-listener-foo" server="Foo.service" />
	<binding client="LifeCycleListenerManagerImpl.petals-state-listener-bar" server="Bar.service" />

Le composant foo.bar.petals.kernel.*Listener implémentent  org.ow2.petals.kernel.api.server.PetalsStateListener et sont ajoutés à la liste des listeners managés par LifeCycleListenerManagerImpl lors de la définition des liens (lignes ‘binding’). Tous les listeners ajoutés au manager doivent avoir une valeur de l’attribut client commençant obligatoirement par LifeCycleListenerManagerImpl.petals-state-listener- (remplissage de la Map par Fractal).

Facile et pratique, non?!

Adding Routing modules in Petals ESB


Petals ESB is based on a highly modular architecture built using software components based on the Fractal component model.

In a previous article about WSDM monitoring (https://chamerling.wordpress.com/2009/11/04/petals-esb-live-monitoring-with-wsdm-and-gwt/), I was speaking about adding routing modules to the ESB router to intercept messages and to notify the monitoring system that messages are transmitted and received between service consumers and providers.

In the current article, I am going to show you how you can add modules to the routing layer of the service bus. In short, the routing layer is the component which select the right endpoint(s) to send the current message to. The routing layer has been decomposed into routing modules which are all called when a message is sent or received.

There are two types of routing modules the SenderModule (org.ow2.petals.jbi.messaging.routing.module.SenderModule) and the ReceiverModule (org.ow2.petals.jbi.messaging.routing.module.ReceiverModule) which both have right names ;). All the sender modules will be called on message emission, the receiver ones will be called at message reception… Here are the Java APIs :

org.ow2.petals.jbi.messaging.routing.module.SenderModule


package org.ow2.petals.jbi.messaging.routing.module;

import java.util.Map;
import org.ow2.petals.jbi.component.context.ComponentContext;
import org.ow2.petals.jbi.messaging.endpoint.ServiceEndpoint;
import org.ow2.petals.jbi.messaging.exchange.MessageExchange;
import org.ow2.petals.jbi.messaging.routing.RoutingException;
import org.ow2.petals.transport.util.TransportSendContext;

public interface SenderModule {

void electEndpoints(final Map<ServiceEndpoint, TransportSendContext> electedEndpoints,

final ComponentContext sourceComponentContext, final MessageExchange exchange)

throws RoutingException;

}

org.ow2.petals.jbi.messaging.routing.module.ReceieverModule


package org.ow2.petals.jbi.messaging.routing.module;

import org.ow2.petals.jbi.component.context.ComponentContext;
import org.ow2.petals.jbi.messaging.exchange.MessageExchange;
import org.ow2.petals.jbi.messaging.routing.RoutingException;

public interface ReceiverModule {

boolean receiveExchange(final MessageExchange exchange,

final ComponentContext sourceComponentContext) throws RoutingException;

}

Implementing and adding modules to the Petals ESB router steps

1. Step one : Create your routing module by implementing the Sender and/or Receiver module interface(s) like, for example, just logging exchange information and date :


package com.googlecode.chamerling.blog.petals.router;

import java.util.Date;

import java.util.Map;

import org.objectweb.fractal.fraclet.annotation.annotations.FractalComponent;

import org.objectweb.fractal.fraclet.annotation.annotations.Interface;

import org.objectweb.fractal.fraclet.annotation.annotations.LifeCycle;

import org.objectweb.fractal.fraclet.annotation.annotations.Monolog;

import org.objectweb.fractal.fraclet.annotation.annotations.Provides;

import org.objectweb.fractal.fraclet.annotation.annotations.type.LifeCycleType;

import org.objectweb.util.monolog.api.Logger;

import org.ow2.petals.jbi.component.context.ComponentContext;

import org.ow2.petals.jbi.messaging.endpoint.ServiceEndpoint;

import org.ow2.petals.jbi.messaging.exchange.MessageExchange;

import org.ow2.petals.jbi.messaging.routing.RoutingException;

import org.ow2.petals.jbi.messaging.routing.module.ReceiverModule;

import org.ow2.petals.jbi.messaging.routing.module.SenderModule;

import org.ow2.petals.transport.util.TransportSendContext;

import org.ow2.petals.util.LoggingUtil;

@FractalComponent

@Provides(interfaces = {

@Interface(name = "logSender", signature = SenderModule.class),

@Interface(name = "logReceiver", signature = ReceiverModule.class) })

public class LogModule implements SenderModule, ReceiverModule {

@Monolog(name = "logger")

private Logger logger;

private LoggingUtil log;

/**

* {@inheritDoc}

*/

public void electEndpoints(

Map<ServiceEndpoint, TransportSendContext> electedEndpoints,

ComponentContext sourceComponentContext, MessageExchange exchange)

throws RoutingException {

this.log.info(LogModule.class.getCanonicalName()

+ " router module is sending a message exchange with ID = "

+ exchange.getExchangeId() + " at " + new Date());

for (ServiceEndpoint endpoint : electedEndpoints.keySet()) {

this.log.info("Selected endpoint : " + endpoint.getEndpointName());

}

// Do what you want...

}

/**

* {@inheritDoc}

*/

public boolean receiveExchange(MessageExchange exchange,

ComponentContext sourceComponentContext) throws RoutingException {

this.log.info(LogModule.class.getCanonicalName()

+ " router module is receiving a message exchange with ID = "

+ exchange.getExchangeId() + " at " + new Date());

// Do what you want...

return false;

}

@LifeCycle(on = LifeCycleType.START)

protected void start() {

this.log = new LoggingUtil(this.logger);

this.log.debug("Starting...");

}

@LifeCycle(on = LifeCycleType.STOP)

protected void stop() {

this.log.debug("Stopping...");

}

}

Note that the Fractal annotations are mandatory!

2. Add the modules to the Fractal descriptor files. The modules MUST be added to the JBI-Messaging.fractal configuration file (located in the src/main/resources of your favorite Petals ESB distribution) :

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE definition PUBLIC "-//ow2.objectweb//DTD Fractal ADL 2.0//EN" "classpath://org/objectweb/fractal/adl/xml/standard.dtd">

<definition extends="JBI-MessagingType" name="JBI-Messaging">

<component definition="org.ow2.petals.jbi.messaging.registry.DistributedEndpointRegistryImpl" name="EndpointRegistryImpl"/>

<component definition="org.ow2.petals.jbi.messaging.routing.RouterServiceImpl" name="RouterServiceImpl"/>

<component definition="org.ow2.petals.jbi.messaging.routing.module.TransportResolverModule" name="TransportResolverModule"/>

<component definition="org.ow2.petals.jbi.messaging.routing.module.EndpointResolverModule" name="EndpointResolverModule"/>

<component definition="org.ow2.petals.jbi.messaging.control.JMXExchangeCheckerClientImpl" name="ExchangeCheckerClientImpl"/>

<!-- My logging module -->
<component definition="com.googlecode.chamerling.blog.petals.router.LogModule" name="LogModuleImpl"/>

<!-- Expose -->

<binding client="this.router" server="MonitoringModuleImpl.service"/>

<binding client="this.transportlistener" server="RouterServiceImpl.transportlistener"/>

<binding client="this.endpoint" server="EndpointRegistryImpl.service"/>

<binding client="this.exchangechecker" server="ExchangeCheckerClientImpl.service"/>

<binding client="this.storage" server="MonitoringStorageServiceImpl.service"/>

<binding client="MonitoringModuleImpl.router" server="RouterServiceImpl.service"/>

<binding client="MonitoringModuleImpl.storageService" server="MonitoringStorageServiceImpl.service"/>

<binding client="EndpointRegistryImpl.configuration" server="this.configuration"/>

<binding client="EndpointRegistryImpl.topology" server="this.topology"/>

<!-- Add logging module to router -->

<binding client="RouterServiceImpl.sendermodule-3" server="LogModuleImpl.logSender"/>

<binding client="RouterServiceImpl.receivermodule-1" server="LogModuleImpl.logReceiver"/>

<!-- router -->

<binding client="RouterServiceImpl.transporter-local" server="this.transporter-local"/>

<binding client="RouterServiceImpl.transporter-tcp" server="this.transporter-tcp"/>

<!--the order of collection of bindings is alphabetically inversed -->

<binding client="RouterServiceImpl.sendermodule-2" server="EndpointResolverModule.service"/>

<binding client="RouterServiceImpl.sendermodule-1" server="TransportResolverModule.service"/>

<binding client="EndpointResolverModule.configuration" server="this.configuration"/>

<binding client="EndpointResolverModule.topology" server="this.topology"/>

<binding client="EndpointResolverModule.endpoint" server="EndpointRegistryImpl.service"/>

<binding client="EndpointResolverModule.checker" server="ExchangeCheckerClientImpl.service"/>

<binding client="TransportResolverModule.configuration" server="this.configuration"/>

<binding client="ExchangeCheckerClientImpl.jmx" server="this.jmx"/>
</definition>

Important things here are :

1. Instantiate the component with line

<component definition="com.googlecode.chamerling.blog.petals.router.LogModule" name="LogModuleImpl"/>

2. Adding the module to sender and receiver modules list :


<binding client="RouterServiceImpl.sendermodule-3" server="LogModuleImpl.logSender"/>

<binding client="RouterServiceImpl.receivermodule-1" server="LogModuleImpl.logReceiver"/>

Note that the lists are alphabetically inversed (The logger module « sendermodule-3 » will be called before the sendermodule-2 and the sendermodule-1 modules).

Here we are, the module will be called at each message emission/reception. Note that this is a basic usage of routing modules, you can do many things with these modules like real time monitoring, security check, authentication, endpoint filtering, content based routing, etc etc etc

Petals ESB v3.0 is out


Petals ESB v3.0 is finally out (two years after the v2.0 release), there are many changes in this version but here are the main ones :

Petals ESB 3.0, includes a lot of improvements for users, as well as optimisations, and nice new features :
– Dynamic configuration, and hot-deployment of new nodes
– Redesigned webconsole for administration, and deleted statistics monitoring (based on OW2-OpenSUIT)
– SCA support: SCA engine (OW2-FraSCAti), and SCA designer (Eclipse)
– BPEL support: BPEL engine, and BPEL designer (Eclipse), with validation (based on a new BPEL engine!)
– WS Notification and WS-BorkeredNotification support (EDA)
– more and more things…

Integration with new Petals Camelia softwares :
– Petals Studio: Complete Eclipse development environment, for Petals ESB
– Petals Master: SOA Governance (OW2-Dragon)
– Petals View: Business flow monitoring based on Petals EDA feature (based on OW2-OpenSUIT)

We gave this release a name : Camelia.

Fractal Component Inheritance Limitations


Here is the problem I had this afternoon and I said really bad things on Fractal and Spoon (if we had a Fractal Box cf  http://wp.me/pcSx0-3U we will be able to have liters of beer to drink tonight…).
Here is the compilation stack trace :
[WARNING] FractalComponentProcessor >>  No value found for property ‘generatorClass’ in processor org.objectweb.fractal.fraclet.annotation.processor.FractalComponentProcessor
[INFO] ————————————————————————
[ERROR] BUILD ERROR
[INFO] ————————————————————————
[INFO] fail to execute

… which is really helpful…

After spending too much time on searching why, the cause was ‘simply’ because my Fractal component (java class) was extending another Fractal component (java class). Not sure that it is a component problem or a component import/requirement one but when one component inherits one other it does not compile at all.

Good to know!

 

Update on november 13:

Sometimes things are strange… It seems that this bug has been fixed some months ago (according to the Fractal bug tracker…) but I have it when I try to extend a component which has already been processed by spoon… ie In the same project all works fine… The problem occurs when I have a fractal based project which depends on another one…