Pages

Monday, June 14, 2010

Enriching and Forwarding your data with the Spring Component in SOA Suite 11g PS2

Enriching your data with the Oracle ESB or with the Mediator component in SOA Suite 11g can be hard. It works perfectly in a one-way ( fire and forget ) situation, like described in this AMIS guide. With the Patch Set 2 of SOA Suite 11g R1 there is a new supported SOA Suite Component called Spring Component which can do this trick perfectly and no limitations.
In this blogpost you will see how easy it is to use Java in the Spring Component, how you can wire this Component to other Components, Services or References adapters. And off course there is no limition in how many times you can enrich or change the data in the Spring Component.
In this example an Employee request with only a Employee Id is enriched with Employee & Department information and this data is forwarded to a logger Spring Component, a processing Mediator and this data is also returned to the initiator of the request. Here is a overview of all the components and adapters


For this example you need an EJB Session Bean and the Employee and Department JPA Entitiy ( Based on the HR demo Schema ). This EJB Session Bean is also exposed as a Web Service.
The HrReference adapter calls the EJB Session Bean interface and the HrWSReference adapter calls the Web Service Interface of this Session Bean. This Model project is included in the example workspace.

Let's start easy with the SpringLogger Spring component. This component will only do a System out. Drop a Spring Component on the composite xml. This create a empty Spring Context XML. Because the plan is to call this component from a other component you need to create a Service interface. For this component this means you need to create a Java Interface class.
package nl.whitehorses.soa.spring.logger;
public interface ILogger {
  public void log (String componentName, 
                   String instanceId, 
                   String message);
}
Add a service to the SpringLogger Component, this service has the java interface as type.
<sca:service target="logger" name="LogServiceEJB"
      type="nl.whitehorses.soa.spring.logger.ILogger"/>
Next step is to make a implementation of this java interface. This will do the processing ( In this case just a System out println )
package nl.whitehorses.soa.spring.logger;

public class LoggerImpl implements ILogger {
    public LoggerImpl() {
    }

    public void log(String componentName, 
                    String instanceId, 
                    String message) {
      StringBuffer logBuffer = new StringBuffer ();
      logBuffer.append("[").append(componentName).append("] [Instance: ").
          append(instanceId).append("] ").append(message);
      System.out.println(logBuffer.toString());
    }
}
And you can add this implementation to the SpringLogger Component, The target attribute of the service references to the name of this bean.
<bean class="nl.whitehorses.soa.spring.logger.LoggerImpl" name="logger"/>
This spring component is finished and you can work on the Employee Spring Component which will do the enriching and forwarding of the data.
Before you can start with the java interface or implementation, you need to make some entities which will be used in this interface.
The department entity
package nl.whitehorses.soa.entities;

public class Department {
    public Department() {
    }

    private Integer departmentId;
    private String name;


    public void setDepartmentId(Integer departmentId) {
        this.departmentId = departmentId;
    }

    public Integer getDepartmentId() {
        return departmentId;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
The employee entity with the department attribute.
package nl.whitehorses.soa.entities;

public class Employee {
    public Employee() {
    }

    private Integer employeeId;
    private String  firstName;
    private String  lastName;
    private String  state;
    private Department department;


    public void setEmployeeId(Integer employeeId) {
        this.employeeId = employeeId;
    }

    public Integer getEmployeeId() {
        return employeeId;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public Department getDepartment() {
        return department;
    }
}
You can now use the employee entity in the java interface class. This interface will be used in the Spring Component Service. The Service has employeeId as parameter and will return the employee entity.
package nl.whitehorses.soa.spring.enrichment;

import nl.whitehorses.soa.entities.Employee;
public interface IEmployee {

  public Employee enrichEmployee(Integer employeeId, 
                                 String  componentName, 
                                 String  instanceId);

}

And the implementation. In the enrichEmployee method you will do all the enrichment and forwarding. We will continuous change this method when we wire all the reference adapters.
package nl.whitehorses.soa.spring.enrichment;

import nl.whitehorses.soa.entities.Employee;

public class EmployeeEnrichmentImpl implements IEmployee {
    public EmployeeEnrichmentImpl() {
    }

    public Employee enrichEmployee(Integer employeeId, 
                                   String  componentName, 
                                   String  instanceId) {
        Employee employee = new Employee();
        return employee;
    }

}
Drop a new Spring Component to the Composite, add a Service and a Bean based on these java classes in the Spring Context XML.
<beans>
   <sca:service target="employee" 
                name="EmployeeServiceEJB"
                type="nl.whitehorses.soa.spring.enrichment.IEmployee"/>

   <bean class="nl.whitehorses.soa.spring.enrichment.EmployeeEnrichmentImpl" 
         name="employee">
   </bean>
</beans>
Wire the reference of the SpringEmployeeEnrichment Component to the service of the SpringLogger Component.
JDeveloper will add a reference in the SpringEmployeeEnrichment Spring Context to the SpringLogger Component.
<beans>

   <sca:service target="employee" name="EmployeeServiceEJB"
      type="nl.whitehorses.soa.spring.enrichment.IEmployee"/>

   <bean class="nl.whitehorses.soa.spring.enrichment.EmployeeEnrichmentImpl" 
         name="employee">
   </bean>

   <sca:reference type="nl.whitehorses.soa.spring.logger.ILogger"
                  name="LogServiceEJB"/>
</beans>
This reference need to be injected to the EmployeeEnrichmentImpl class. Add a property to the bean with a reference to the SpringLogger service.
<bean class="nl.whitehorses.soa.spring.enrichment.EmployeeEnrichmentImpl" 
      name="employee">
   <property name="loggerReference"   
             ref="LogServiceEJB" />
</bean>
Add the loggerReference variable ( use the ILogger interface and this must match with the property name of the Spring Bean ) to EmployeeEnrichmentImpl and generate the Accessors. Spring will inject the ILoggerImpl to this variable. In the enrichEmployee method you can call the log method and pass on the parameters.
package nl.whitehorses.soa.spring.enrichment;

import nl.whitehorses.soa.entities.Employee;
import nl.whitehorses.soa.spring.logger.ILogger;

public class EmployeeEnrichmentImpl implements IEmployee {
    public EmployeeEnrichmentImpl() {
    }

    private ILogger loggerReference;

    public Employee enrichEmployee(Integer employeeId, 
                                   String  componentName, 
                                   String  instanceId) {
        // log the request.
        loggerReference.log(componentName, instanceId, "Enrich employee: "+employeeId);
        Employee employee = new Employee();

        return employee;
    }
   
    public void setLoggerReference(ILogger loggerReference) {
        this.loggerReference = loggerReference;
    }

    public ILogger getLoggerReference() {
        return loggerReference;
    }
}

The next step is to expose the Service of the Spring Component. Drag the service to the Exposed Services part of the composite. So you can invoke this service.
You can now choose to expose this as an EJB or a Web Service. Choose Web Service so you can invoke this service in the Enterprise manager.
With this as result.
This is not the best way to expose this Spring Component Service, every change in the java interface will lead to a different WSDL, so let's add a Mediator with an Employee XML Schema to solve this. Now you can change the WSDL or the Java Interface of the Spring Service and only need to update the transformation in the routing rule of the Mediator.
Wire the Mediator to the Spring Component service and add the request and response transformations ( in the routing rule )

Now we can return to the Enrichment part by adding a EJB and Web Service reference adapter and wire this to the Spring component.
First we use the EJB adapter to lookup the employee. You need to change the Implementation by adding a variable based on the Remote Interface of the EJB Session Bean. Add the Service Interface jar of the EJB Session Bean to the dependency of the SOA Project and also put it in the SCA-INF/lib folder. The enrichEmployee method will invoke this EJB and converts the result to a local employee entity.

package nl.whitehorses.soa.spring.enrichment;

import nl.whitehorses.soa.entities.Employee;
import nl.whitehorses.soa.model.hr.entities.Employees;
import nl.whitehorses.soa.spring.logger.ILogger;
import nl.whitehorses.soa.model.hr.services.HrModelSessionEJB;


public class EmployeeEnrichmentImpl implements IEmployee {
    public EmployeeEnrichmentImpl() {
    }

    private ILogger loggerReference;
    private HrModelSessionEJB hrModelReference;

    public Employee enrichEmployee(Integer employeeId, 
                                   String  componentName, 
                                   String  instanceId) {

        // log the request.
        loggerReference.log(componentName, instanceId, "Enrich employee: "+employeeId);

        // get the employee from the SOA Suite Reference
        Employees emp = hrModelReference.getEmployeesFindOne(employeeId);
        
        Employee employee = new Employee();
        employee.setEmployeeId(emp.getEmployeeId().intValue());
        employee.setFirstName(emp.getFirstName());
        employee.setLastName(emp.getLastName());
        employee.setState("logged");

        return employee;
    }

   
    public void setLoggerReference(ILogger loggerReference) {
        this.loggerReference = loggerReference;
    }

    public ILogger getLoggerReference() {
        return loggerReference;
    }

    public void setHrModelReference(HrModelSessionEJB hrModelReference) {
        this.hrModelReference = hrModelReference;
    }

    public HrModelSessionEJB getHrModelReference() {
        return hrModelReference;
    }

}
Add a new Spring Context Reference based on this Remote interface of the EJB Session Bean. Also add a new property to the Spring Bean which matches with the variable in the Implementation.
<beans>

   <sca:service target="employee" 
                name="EmployeeServiceEJB"
                type="nl.whitehorses.soa.spring.enrichment.IEmployee"/>

   <bean class="nl.whitehorses.soa.spring.enrichment.EmployeeEnrichmentImpl" 
         name="employee">
      <property name="loggerReference"   ref="LogServiceEJB" />
      <property name="hrModelReference"  ref="HrServiceEJB" />
   </bean>

   <sca:reference type="nl.whitehorses.soa.spring.logger.ILogger"
                  name="LogServiceEJB"/>

   <sca:reference name="HrServiceEJB"
                  type="nl.whitehorses.soa.model.hr.services.HrModelSessionEJB"/>
</beans>
Add a new wire for the EJB Reference Adapter by Dragging the not used reference of the Spring Component to the external references of the Composite.
Choose the EJB option.
With this as result.
Open the EJB Reference Adapter and update the JNDI name. ( I deployed the EJB Session Bean on the Soa Suite Server)
The EJB Reference Adapter is finished and wired to the Spring Component.

The next step is to add a Web Service Reference to the Spring Component, I will use this WS to lookup the department of the employee.When you want to use a Web Service in the Spring Component you need to add the Web Service Reference adapter first ( Because I don't have a Web Service Client Proxy )
Add a Web Service Reference Adapter to your Composite.
Wire the WS to the Spring Component Reference.
JDeveloper will generate a WS proxy client and add a Reference to the Spring Context.
<sca:reference type="nl.whitehorses.soa.model.hr.services.HrSessionBeanService"
                      name="HrWSReference"/>
And add a property to this reference on the Spring Context bean. Change the Spring Component implementation so you can invoke the Web Service to lookup the department.
package nl.whitehorses.soa.spring.enrichment;

import nl.whitehorses.soa.entities.Department;
import nl.whitehorses.soa.entities.Employee;
import nl.whitehorses.soa.model.hr.entities.Employees;
import nl.whitehorses.soa.spring.logger.ILogger;
import nl.whitehorses.soa.model.hr.services.HrModelSessionEJB;
import nl.whitehorses.soa.model.hr.services.HrSessionBeanService;

public class EmployeeEnrichmentImpl implements IEmployee {
    public EmployeeEnrichmentImpl() {
    }

    private ILogger loggerReference;
    private HrModelSessionEJB hrModelReference;
    private HrSessionBeanService hrWSModelReference;

    public Employee enrichEmployee(Integer employeeId, 
                                   String  componentName, 
                                   String  instanceId) {

        // log the request.
        loggerReference.log(componentName, instanceId, "Enrich employee: "+employeeId);

        // get the employee from the SOA Suite Reference
        Employees emp = hrModelReference.getEmployeesFindOne(employeeId);
        
        // get the department from the WebLogic SCA Reference
        nl.whitehorses.soa.model.hr.services.Departments dept = hrWSModelReference.getDepartmentsFindOne(emp.getDepartmentId());
        
        Department department  = new Department();
        department.setDepartmentId(dept.getDepartmentId().intValue());
        department.setName(dept.getDepartmentName());
        
        Employee employee = new Employee();
        employee.setEmployeeId(emp.getEmployeeId().intValue());
        employee.setFirstName(emp.getFirstName());
        employee.setLastName(emp.getLastName());
        employee.setDepartment(department);
        employee.setState("logged");

        return employee;
    }

   
    public void setLoggerReference(ILogger loggerReference) {
        this.loggerReference = loggerReference;
    }

    public ILogger getLoggerReference() {
        return loggerReference;
    }

    public void setHrModelReference(HrModelSessionEJB hrModelReference) {
        this.hrModelReference = hrModelReference;
    }

    public HrModelSessionEJB getHrModelReference() {
        return hrModelReference;
    }

    public void setHrWSModelReference(HrSessionBeanService hrWSModelReference) {
        this.hrWSModelReference = hrWSModelReference;
    }

    public HrSessionBeanService getHrWSModelReference() {
        return hrWSModelReference;
    }

}

The last part is to connect a Mediator to the Spring Component and pass on the Employee entity so you can for example write this object to a file or insert the employee in a table.
Add a Mediator Component to the Composite and use the Define Interface Later Template.
Create a new Java Interface for this Mediator with the entity you want to pass on.
package nl.whitehorses.soa.spring.enrichment;

import nl.whitehorses.soa.entities.Employee;

public interface IEmployeeMediator {
    
    public void processEmployee ( Employee emp );
}
Change the Spring Component xml, add the reference and the property on the bean
<beans>

   <sca:service target="employee" name="EmployeeServiceEJB"
      type="nl.whitehorses.soa.spring.enrichment.IEmployee"/>

   <bean class="nl.whitehorses.soa.spring.enrichment.EmployeeEnrichmentImpl" 
         name="employee">
      <property name="loggerReference"   ref="LogServiceEJB" />
      <property name="hrModelReference"  ref="HrServiceEJB" />
      <property name="hrWSModelReference"  ref="HrWSReference" />
      <property name="mediatorReference" ref="EmployeeMediator" />
   </bean>

   <sca:reference type="nl.whitehorses.soa.spring.enrichment.IEmployeeMediator"
                  name="EmployeeMediator"/>

   <sca:reference type="nl.whitehorses.soa.spring.logger.ILogger"
                  name="LogServiceEJB"/>

   <sca:reference name="HrServiceEJB"
                  type="nl.whitehorses.soa.model.hr.services.HrModelSessionEJB"/>

   <sca:reference type="nl.whitehorses.soa.model.hr.services.HrSessionBeanService"
                  name="HrWSReference"/>
</beans>
Change the implementation for the Mediator
package nl.whitehorses.soa.spring.enrichment;

import nl.whitehorses.soa.entities.Department;
import nl.whitehorses.soa.entities.Employee;
import nl.whitehorses.soa.model.hr.entities.Employees;
import nl.whitehorses.soa.spring.logger.ILogger;
import nl.whitehorses.soa.model.hr.services.HrModelSessionEJB;
import nl.whitehorses.soa.model.hr.services.HrSessionBeanService;

public class EmployeeEnrichmentImpl implements IEmployee {
    public EmployeeEnrichmentImpl() {
    }

    private ILogger loggerReference;
    private HrModelSessionEJB hrModelReference;
    private HrSessionBeanService hrWSModelReference;
    private IEmployeeMediator mediatorReference;

    public Employee enrichEmployee(Integer employeeId, 
                                   String  componentName, 
                                   String  instanceId) {

        // log the request.
        loggerReference.log(componentName, instanceId, "Enrich employee: "+employeeId);

        // get the employee from the SOA Suite Reference
        Employees emp = hrModelReference.getEmployeesFindOne(employeeId);
        
        // get the department from the WebLogic SCA Reference
        nl.whitehorses.soa.model.hr.services.Departments dept = hrWSModelReference.getDepartmentsFindOne(emp.getDepartmentId());
        
        Department department  = new Department();
        department.setDepartmentId(dept.getDepartmentId().intValue());
        department.setName(dept.getDepartmentName());
        
        Employee employee = new Employee();
        employee.setEmployeeId(emp.getEmployeeId().intValue());
        employee.setFirstName(emp.getFirstName());
        employee.setLastName(emp.getLastName());
        employee.setDepartment(department);
        employee.setState("logged");
        
        mediatorReference.processEmployee(employee);

        return employee;
    }

   
    public void setLoggerReference(ILogger loggerReference) {
        this.loggerReference = loggerReference;
    }

    public ILogger getLoggerReference() {
        return loggerReference;
    }

    public void setHrModelReference(HrModelSessionEJB hrModelReference) {
        this.hrModelReference = hrModelReference;
    }

    public HrModelSessionEJB getHrModelReference() {
        return hrModelReference;
    }


    public void setHrWSModelReference(HrSessionBeanService hrWSModelReference) {
        this.hrWSModelReference = hrWSModelReference;
    }

    public HrSessionBeanService getHrWSModelReference() {
        return hrWSModelReference;
    }

    public void setMediatorReference(IEmployeeMediator mediatorReference) {
        this.mediatorReference = mediatorReference;
    }

    public IEmployeeMediator getMediatorReference() {
        return mediatorReference;
    }
}
Add a wire between the not used reference of the Spring Component and the service of the Mediator.

At last add a File or a Database Adapter to the Composite and wire this to this Mediator.

That's all.

Here is the example workspace.

7 comments:

  1. Very nice blog showing all binding possibilities of the Spring component.
    I also like the usage of the first mediator to decouple the Java interface from the WS interface (WSDL).

    ReplyDelete
  2. Hi Edwin,

    I was trying to invoke an EJB from the Spring component just like you shows in this post but I always got an error when invoking EJB (oracle.fabric.common.FabricInvocationException: Unable to invoke) caused by another exception (java.lang.IllegalArgumentException: argument type mismatch). The log suggestions are to check the config and to add "WebMethod operationName" in my EJB interface. It seems like its trying to invoke the EJB as a webservice but I don't know why.
    I did exactly what you wrote in order to configure the EJB reference and I already checked a lot of times my configuration but I can't find a solution =(. Ddo you have an advice?? I'm using SOA Suite 11gR3

    ReplyDelete
  3. Hi,

    Did you test the EJB with a test client where you do a jndi lookup and use interface.

    if that works you can use the same interface in spring , then there should be no mismatch.

    else make a very simple testcase and send it to me biemond at gmail dot com

    thanks

    ReplyDelete
  4. I found something weird, I was using an EJB from another project. A simple java client works fine with my old EJB, but the invocation from the spring SOA component always fails. I created a simple EJB to do the test and this can be invoked great, also I could invoke other methods from my old EJB that returns simple types, so the problem was in my old EJB method. My old EJB returns a hibernate annotated bean and have more dependencies. I put all the jar dependencies in SCA-INF/lib, it seems like the EJB Adapter have some problems with complex objects. Or maybe I'm putting the dependencies in the wrong place, is there another place to put them?

    Thank you very much for your help

    ReplyDelete
  5. Interesting blog article. We are finding Spring composites useful for integrating legacy java code but we have a problem with exceptions. When checked exceptions are thrown from the interface the generated WSDL picks up on these and exposes them as faults at the point of generation but when the service is called the fault isn't as expected. We are only seeing the generic fault message with faultstring
    oracle.fabric.common.FabricException Invocation failed: com.test.MyException

    Annother question what technology is the spring composite built on, I'm guessing it's not Spring-ws as that is contract first, is it then JAX-WS or something bespoke to Oracle? Where can I find more documentation on this?

    ReplyDelete
    Replies
    1. Hi,

      I think the fault handler get the fault first, maybe you can handle it with a custom java fault handler.
      I know there are 2 versions
      first is the weblogic sca http://biemond.blogspot.nl/2011/12/weblogic-sca-with-weblogic-12c-and-oepe.html

      and second one is inside soa suite. I think they made a service component around spring.
      the code should be somewhere in the fabric jars.

      good luck

      Delete