Using the event API to publish an event to the Event Delivery Network (EDN) – the Spring way


The Event Delivery Network (EDN) in Oracle SOA Suite 11g provides a declarative way to use a publish/subscribe model to generate and consume business events without worrying about the underlying message infrastructure. Events can be published / subscribed from a variety of programming environments such as Java, PL/SQL, SOA Composites, and ADF-BC applications

In his blog Clemens showed how the Event API can be used to publish an event programmatically to the Event Delivery Network (EDN). I took the sample code Clemens provided and did some refactoring to make it more “Spring like”.

My idea is to use some of the features of the Spring Framework to simplify the publishing of Business Events to EDN from Java, without worrying about the details of the Java event API. The following features are of interest:

    The goal is to make publishing an Business Event to EDN from Java as simple as that:
// create a new customer
Customer cust = new Customer();
cust.setId(1);
cust.setFirstName("Guido");
cust.setLastName("Schmutz");

Address adr = new Address();
adr.setStreet("Papiermuehlestrasse 73");
adr.setZipCode(3014);
adr.setCity("Bern");
adr.setCountry("Switzerland");
cust.setAddress(adr);

// create a new customer event, passing the customer object as the payload
NewCustomerEvent event = new NewCustomerEvent(this, cust);

// publish the event through the Spring Event Handling mechanism
context.publishEvent(event);

    We just use simple Java objects (POJOs) to create an event and its payload and then publish it through the Spring application context as a standard Spring event.
    Sounds interesting to you? Yes, but what is needed behind the scene to make this work?
    Before we start to dig into the Java implementation, let’s first create the Business Event inside the Oracle SOA Suite 11g and implement a composite subscribing to this event.

    First, you need to define the data-shape of the event. This is done conventionally through the usage of XML Schema (File customer.xsd).

    <?xml version="1.0" encoding="windows-1252" ?>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:cus="http://www.trivadis.com/cdm/customer"
                targetNamespace="http://www.trivadis.com/cdm/customer"
                elementFormDefault="qualified">
      <xsd:element name="customer">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="id" type="xsd:integer"/>
            <xsd:element name="firstName" type="xsd:string"/>
            <xsd:element name="lastName" type="xsd:string"/>
            <xsd:element name="address">
              <xsd:complexType>
                <xsd:sequence>
                  <xsd:element name="street" type="xsd:string"/>
                  <xsd:element name="city" type="xsd:string"/>
                  <xsd:element name="zipCode" type="xsd:integer"/>
                  <xsd:element name="country" type="xsd:string"/>
                </xsd:sequence>
              </xsd:complexType>
            </xsd:element>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>
    
    

     

    After that the Business Event can be defined using the Event Definition Language (EDL).

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <definitions xmlns="http://schemas.oracle.com/events/edl"
                 targetNamespace="http://www.trivadis.com/events/edl/CustomerEventDefinition">
      <schema-import namespace="http://www.trivadis.com/cdm/customer"
                     location="xsd/customer.xsd"/>
      <event-definition name="NewCustomer">
        <content xmlns:ns0="http://www.trivadis.com/cdm/customer"
                 element="ns0:customer"/>
      </event-definition>
    </definitions>
    

     

    At last we can publish/subscribe to that event from a SOA Suite composite. For our example, I have defined a simple Mediator component listening on the NewCustomer event, which is then sent to a BPEL component for processing.

    tmp

       

      After deploying this SCA composite to the server, we are ready on the SOA Suite side to consume NewCustomer events. Now let’s implement the producer side in Java.

      Defining the Event in Java


      We start with the payload (the information) of the event. As shown in the Java snippet above, the goal is to pass the event payload as a normal Java object, without having to worry about the XML representation necessary for publishing the event through the API.

      The act of converting an XML document to/from an object is called Object/XML Mapping or O/X Mapping for short. There are quite a lot of Java frameworks supporting this Object/XML Mapping, like JAXB, Castor, XMLBean, JiBX or XStream. For this post I will use the JiBX framework, as it offers a flexible approach when starting with an XML Schema.

      In my previous post Using the new Object/XML Mapping Support of Spring 3.0 with JiBX and Maven I have presented how to work with JiBX from Spring using Maven as the build tool. The same approach I will use here as well.

      With the XML Schema customer.xsd used for the BusinessEvent definition above as input, the JiBX framework generates the two Java classes Customer and Address defining the payload of our event in Java.

      public class Customer
      {
          private int id;
          private String firstName;
          private String lastName;
          private Address address;
      
          // public getter and setter method not shown
      }
      
      public class Address
      {
          private String street;
          private String city;
          private int zipCode;
          private String country;
      
          // public getter and setter method not shown
      }
      
      

      Next let’s define the NewCustomerEvent class, which defines the event itself and acts as a wrapper for the event payload.

      public class NewCustomerEvent extends AbstractApplicationEventForEDN {
      
      	private Customer customer;
      	private final static String NAMESPACE = "http://www.trivadis.com/events/edl/CustomerEventDefinition";
      	private final static String NAME = "NewCustomer";
      
      	
      	public NewCustomerEvent(Object source, Customer customer) 
                                throws XmlMappingException, ParserConfigurationException, IOException {
      		super(source, NAMESPACE, NAME, customer);
      	}
      }
      
      

      The payload, customer in the case here, can be passed in the 2nd argument of the constructor. The other properties necessary for a Business Event in EDN (namespace and name) are defined as constant values, passed to the constructor of the parent class.
      NewCustomerEvent inherits from AbstractApplicationEventForEDN, the base class for all events to be published to EDN. It defines the common properties of an Business Event, like namespace, local name, content, event id and conversation id.

      public abstract class AbstractApplicationEventForEDN extends ApplicationEvent {
      
      	private String namespace;
      	private String name;
      	private Object content;
      	private Object eventId;
      	private Object conversationId;
      
      	public AbstractApplicationEventForEDN(Object source, String namespace, String name, Object content) throws ParserConfigurationException, XmlMappingException, IOException {
      		super(source);
      		
      		this.namespace = namespace;
      		this.name = name;
      		this.content = content;
      		this.eventId = UUID.randomUUID();
      		this.conversationId = UUID.randomUUID();
      	}
      
      	// setter method not shown
      }
      
      

      AbstractApplicationEventForEDN itself inherits from ApplicationEvent, a class provided by the Spring event handling mechanism. This means that such a Java Business Event is automatically a Spring ApplicationEvent so that it can be published in the Spring event handling mechanism like any other Spring event.

      So far we have a Java representation of the Business Event with its payload and we are able to publish these events through Spring.
      Next we will see how we can connect to EDN and publish the Business Events in a Spring way.

      Creating a FactoryBean to abstract away the creation of a BusinessEventConnection

      First let’s start with the link from Java to the Event Delivery Network. To talk to EDN, we need an instance of oracle.fabric.blocks.event.BusinessEventConnection. To create such an instance, the EDN Java API provides a factory interface named BusinessEventConnectionFactory together with an implementation through SAQRemoteBusinessEventConnectionFactory.
      I use such a FactoryBean to wrap the creation of a BusinessEventConnection.

      public class BusinessEventConnectionFactoryBean implements FactoryBean, InitializingBean {
      
      	DataSource dataSource;
      	BusinessEventConnectionFactory businessEventConnectionFactory;
      	
      	public void setDataSource(DataSource dataSource) {
      		this.dataSource = dataSource;
      	}
      
      	public void afterPropertiesSet() throws Exception {
              businessEventConnectionFactory = 
                  new SAQRemoteBusinessEventConnectionFactory(
                      dataSource, dataSource, null);
      	}
      
      	public Object getObject() throws Exception {
      		return businessEventConnectionFactory.createBusinessEventConnection();
      	}
      
      	public Class getObjectType() {
      		return BusinessEventConnection.class;
      	}
      
      	public boolean isSingleton() {
      		return false;
      	}
      }
      
      
      BusinessEventConnectionFactoryBean  declares a dependency to a DataSource, which is necessary for creating a BusinessEventConnectionFactory. This means that the DataSource needs to be injected through Spring, so a bean configuration is necessary in the Spring context XML definition
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <bean id="businessEventConnection" class="com.trivadis.soa.BusinessEventConnectionFactoryBean">
    	<property name="dataSource" ref="dataSource"/>
    </bean>
    	
    <bean id="dataSource" class="oracle.jdbc.xa.client.OracleXADataSource">
    	<property name="user" value="${jdbc.username}"/>
    	<property name="password" value="${jdbc.password}"/>
    	<property name="URL" value="${jdbc.url}"/>
    	<property name="implicitCachingEnabled" value="false"/>
    </bean>
    

     

    The DB connection properties can be externalized into a properties file (jdbc.properties) by using the Spring context:property-placeholder element.

    jdbc.url=jdbc:oracle:thin:@soavm11.trivadis.com:1521:repoas
    jdbc.username=dev_soainfra
    jdbc.password=oracle
    

     

    When running in an Java EE application server, the DataSource can easily be retrieved via JNDI by switching the bean configuration as follows

    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" value="jdbc/MyDataSource"/>
    </bean>
    
    

     

    With the BusinessEventConnection bean set up, let’s see how the Java event classes (i.e. NewCustomerEvent) can be published to EDN.

       

      Publishing the Event to the Event Delivery Network (EDN)

    According to Spring Best Practices we first define an interface which the event publisher implementation will need to implement. The publishEvent() method declares a parameter of type AbstractApplicationEventForEDN, so any child class extending it will be accepted and published.

    public interface BusinessEventPublisher {
    
    	public abstract void publishEvent(AbstractApplicationEventForEDN applicationEvent)
    			throws XmlMappingException, IOException;
    
    }
    

     

    My implementation of the BusinessEventPublisher can be seen below with the BusinessEventPublisherEDN class. The publishEvent() method first marshals the payload (content property) of the AbstractApplicationEventForEDN into an XML document using the Spring O/X Mapping support. The Marshaller implementation to be used is injected by Spring at initialization time (for more information see my other blog post).

    After that an instance of oracle.fabric.common.BusinessEvent is created and published via the BusinessEventConnection, which is also injected by Spring (i.e. the bean declared before).

    public class BusinessEventPublisherEDN implements BusinessEventPublisher {
    
    	private BusinessEventConnection conn;
    	private Marshaller marshaller;
    
    	public void setBusinessEventConnection(BusinessEventConnection conn) {
    		this.conn = conn;
    	}
    
    	public void setMarshaller(Marshaller marshaller) {
    		this.marshaller = marshaller;
    	}
    
    	public void publishEvent(AbstractApplicationEventForEDN applicationEvent) throws XmlMappingException, IOException {
    		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
    				.newInstance();
    		DocumentBuilder builder = null;
    		try {
    			builder = documentBuilderFactory.newDocumentBuilder();
    		} catch (ParserConfigurationException e) {
    			e.printStackTrace();
    		}
    		Document document = builder.newDocument();
    		DOMResult result = new DOMResult(document);
    
    		marshaller.marshal(applicationEvent.getContent(), result);
    		Element payload = document.getDocumentElement();
    
    		BusinessEvent event = buildEvent(applicationEvent, payload);
    		conn.publishEvent(event, 3);
    	}
    
    	private BusinessEvent buildEvent(
    			AbstractApplicationEventForEDN applicationEvent, Element payload) {
    		BusinessEventBuilder beb = BusinessEventBuilder.newInstance();
    		QName eventName = new QName(applicationEvent.getNamespace(), applicationEvent
    				.getName());
    		beb.setEventName(eventName);
    		beb.setBody(payload);
    		beb.setProperty(BusinessEvent.EVENT_ID, applicationEvent.getEventId());
    		beb.setProperty(BusinessEvent.PROPERTY_CONVERSATION_ID, applicationEvent
    				.getConversationId());
    		beb.setProperty(BusinessEvent.PRIORITY, 1);
    		BusinessEvent be = beb.createEvent();
    		return be;
    	}
    }
    
    

     

    I could use this EventPublisher implementation directly to publish events by injecting it all the event producer beans.

    But my idea is to use the Spring event handling mechanism as the base event transport inside the Spring application and to have one common/central place where all Spring event of type AbstractApplicationEventForEDN are forwarded to Oracle EDN.

    For that I have implemented the Spring ApplicationListener using the AbstractApplicationEventForEDN base class as the type parameter. This has the effect that the listener does only subscribe to subclasses of AbstractApplicationEventForEDN, i.e. all my Java EDN events. The listener uses the injected BusinessEventPublisher instance to publish the events to EDN. This listener implementation basically acts as a bridge from the Spring event handling mechanism to the Event Delivery Network.

    public class ApplicationEventListenerEDN implements ApplicationListener<AbstractApplicationEventForEDN> {
    
    	private BusinessEventPublisher businessEventPublisher;
    	
    	public void setBusinessEventPublisher(
    			BusinessEventPublisher businessEventPublisher) {
    		this.businessEventPublisher = businessEventPublisher;
    	}
    
    	public void onApplicationEvent(AbstractApplicationEventForEDN applicationEvent) {
    		try {
    			businessEventPublisher.publishEvent(applicationEvent);
    		} catch (XmlMappingException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    The configuration of this classes as Spring beans and the dependencies between them is shown here:

    <bean id="applicationEventListener" class="com.trivadis.soa.ApplicationEventListenerEDN">
    	<property name="businessEventPublisher" ref="businessEventPublisher"/>
    </bean>
    
    <bean id="businessEventPublisher" class="com.trivadis.soa.BusinessEventPublisherEDN">
    	<property name="businessEventConnection" ref="businessEventConnection"/>
    	<property name="marshaller" ref="marshaller"/>
    </bean>
    
    <oxm:jibx-marshaller id="marshaller" target-class="com.trivadis.cdm.product.Product"/>
    

     

    That’s it! The Java event producer implementation is ready to be tested.

       

      Testing the publishing of an Event


      I have used the Spring Integration Testing support to test the event publishing. The test class PublisherTest implements the Spring ApplicationContextAware interface. This will force the class to implement the setApplicationContext() method, so that the Spring ApplicationContext will be injected at runtime. This ApplicationContext is necessary to publish an event to the Spring event handling mechanism.

      @RunWith(SpringJUnit4ClassRunner.class)
      //ApplicationContext will be loaded from "classpath:/com/trivadis/soa/PublisherTest-context.xml"
      @ContextConfiguration
      public class PublisherTest implements ApplicationContextAware  {
      	
      	private ApplicationContext context;
      
      	public void setApplicationContext(ApplicationContext context) throws BeansException {
      		this.context = context;
      	}
      
      	@Test
      	public void testPublishNewCustomer() throws Exception {
      		Customer cust = new Customer();
      		cust.setId(1);
      		cust.setFirstName("Guido");
      		cust.setLastName("Schmutz");
      		Address adr = new Address();
      		adr.setStreet("Papiermuehlestrasse 73");
      		adr.setZipCode(3014);
      		adr.setCity("Bern");
      		adr.setCountry("Switzerland");
      		cust.setAddress(adr);
      			
      		NewCustomerEvent event = new NewCustomerEvent(this, cust);
      		context.publishEvent(event);
      	}
      }
      

      The test method uses the code shown at the beginning of this post. So the goal of simplifying the event publishing to EDN from Java has been achieved!

      The Enterprise Manager console proves that the event publishing to EDN works!

      temp[7]

    Above the Flow Trace overview and below the details for the EventConsumer is shown. 

        temp[9]

         

        Source code

         

        The code shown in this post is only a proof-of-concept and before using it in a real project, some things like error handling need to be improved. All the source code can be downloaded from here.

        In addition to the NewCustomerEvent shown above, the downloadable version implements two other events, UpdateCustomerEvent and NewProductEvent.

      The download contains he following projects:

        Project IDE/Build Description
        spring-edn Eclipse/Maven all reusable classes described in my post
        spring-edn-sample Eclipse/Maven simulates a Spring application with the Business Event class definition. Depends on the spring-edn project and references these classes when setting up the Spring context at startup time.
        maven-local-repo Maven Handles JAR’s not available in public Maven Repositories but necessary for the build of the two projects. There is a script which will install these JARs into the local repository on your machine. Except of the jms.jar, the JAR files are not provided here (because I don’t know if I would be allowed to), they are copied from your own local installation when running the script.
        EDNProject JDeveloper SOA Suite 11g project defining the Business Event for the Event Delivery Network (EDN) and the composite subscribing to the event on EDN.
        In order for the JiBX customization to work when building the spring-edn-sample project, a patched version of the JiBX maven plugin (1.2.1.1a) is necessary, downloadable from here. For more information see my previous post. For the deployment of the projects, follow these steps:
      1. deploy the EDNProject to a SOA Suite 11g instance.
      2. install the missing JAR’s into your local maven repository by running maven-local-repo/install-jars.bat. Don’t forget to change the environment variable to point to your local installation of FMW
      3. install the necessary artifacts to your local maven repository
      4. create the spring-edn project using mvn install
      5. create the spring-edn-sample project using mvn install.

      Now you can test the installation by running the unit test class as shown above! Hope your are successful!

      Advertisements