Passing java arrays in oracle stored procedure from mule esb flow

14
Priyobroto Ghosh | Passing Java Arrays in Oracle Stored Procedure from Mule ESB Flow | April 11, 2016 Passing Java Arrays in Oracle Stored Procedure from Mule ESB Flow INVOKING ORACLE STORED PROCEDURE FROM MULE FLOW USING JAVA ARRAYS

Transcript of Passing java arrays in oracle stored procedure from mule esb flow

Page 1: Passing java arrays in oracle stored procedure from mule esb flow

Priyobroto Ghosh | Passing Java Arrays in Oracle Stored Procedure from Mule ESB Flow | April 11, 2016

Passing Java Arrays in Oracle Stored Procedure from Mule ESB Flow INVOKING ORACLE STORED PROCEDURE FROM MULE FLOW USING JAVA

ARRAYS

Page 2: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 1

Objective The objective of this tutorial is to demonstrate the workaround needed to invoke an Oracle Stored Procedure from Mule ESB flow by passing Java arrays as parameters. The use case for this tutorial is a simple one such as inserting student records from a CSV file into an Oracle database table through a stored procedure where one of the parameters is an array holding the student’s marks.

Pre-requisites 1. Oracle XE 11.2g: http://www.oracle.com/technetwork/database/database-technologies/express-

edition/downloads/index.html 2. Java SE 8: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-

2133151.html 3. Mule Anypoint Studio 5.4 with 3.7.3 Runtime: https://www.mulesoft.com/lp/dl/studio 4. Mule ESB 3.7.3 (Optional): https://developer.mulesoft.com/download-mule-esb-runtime 5. Mule Application Properties (mule-app.properties): Use the following properties or define them

as per your system. db.host=localhost db.port=1521 db.instance=XE db.user=scott db.password=tiger

Backend Concepts Let’s see few concepts required from Oracle PL/SQL perspective to fulfill this use case.

1. To create an array in PL/SQL, first you have to create a user defined type and then use the type to create a field or parameter. For our use case, we are supposed to create a type as TABLE OF NUMBER.

CREATE OR REPLACE TYPE NUMBER_T IS TABLE OF NUMBER;

Page 3: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 2

2. Create the database table named STUDENTS, create the primary key constraint, create an index, and create the sequence for automatically creating the next sequence for STUDENT_ID.

CREATE TABLE STUDENTS (STUDENT_ID INTEGER NOT NULL, NAME VARCHAR2(100), ENG NUMBER, PHY NUMBER, CHEM NUMBER, MATH NUMBER); CREATE UNIQUE INDEX STUDENTS_IDX1 ON STUDENTS(STUDENT_ID); ALTER TABLE STUDENTS ADD CONSTRAINT STUDENTS_PK PRIMARY KEY (STUDENT_ID) USING INDEX; CREATE SEQUENCE STUDENTS_SEQ START WITH 1 INCREMENT BY 1 CACHE 10000; 3. Create the Stored Procedure which should be defined with two parameters. The first parameter

is for the name of the student and the second parameter is for holding the marks and therefore, should be of type NUMBER_T.

CREATE OR REPLACE PROCEDURE INSERT_STUDENTS (I_NAME VARCHAR2, I_MARKS NUMBER_T) IS BEGIN INSERT INTO STUDENTS (STUDENT_ID, NAME, ENG, PHY, CHEM, MATH) VALUES STUDENTS_SEQ.NEXTVAL, I_NAME, I_MARKS (1), I_MARKS (2), I_MARKS (3), I_MARKS (4)); END INSERT_STUDENTS; /

Note: The parameter I_MARKS defined above is considered as an array of numbers in Oracle PL/SQL and NUMBER_T is known as a user defined type.

The parameters I_NAME and I_MARKS are considered as IN parameters, means input parameters.

Mule Application The Mule application flow begins with a File inbound endpoint that reads a ‘student.csv’ file from ‘src/main/resources/input’ folder and moves the file to ‘src/main/resources/output’ folder after reading is completed. The read contents are then transformed into a collection of Java objects of type ‘student.Student’ through the Transform Message component. Once the transformation is successfully completed, the transformed payload is iterated using For Each scope wrapped with a Database Connector in order to invoke the Oracle stored procedure with two parameters where the first parameter is the student’s name and the second parameter is the student’s marks as a Java array. The following figure depicts the project structure for this application.

Page 4: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 3

Resources This application uses one CSV file name ‘student.csv’ as a resource containing the student’s records which are then processed and stored in the underlining Oracle database table SCOTT.STUDENTS with the help of a stored procedure invocation. The below figure depicts the structure of the CSV file:

Name,Eng,Phy,Chem,Math Praveen Kumar,96.0,77.0,85.0,89.0 Kamlesh Yadav,78.0,65.0,56.0,45.0 Manoj Kumar,89.0,77.0,86.0,98.0 Rajesh Mistry,87.0,56.0,45.0,89.0 Pankaj Sinha,77.0,89.0,76,0,56.0 Sumit Kumar,89.0,67.0,75.0,66.0

The CSV file is placed in src/main/resources/input folder.

Page 5: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 4

Global Configurable Elements This application requires the following global configurable elements:

1. Oracle Configuration <db:oracle-config name="Oracle_Configuration" host="${db.host}" port="${db.port}" instance="${db.instance}" user="${db.user}" password="${db.password}" doc:name="Oracle Configuration"> </db:oracle-config> 2. Global Function Configuration

The main purpose of this global function is to create an oracle.sql.ARRAY object with the given type name NUMBER_T and the elements of the Java array (Student Marks) so that the marks parameter of the invoking stored procedure can be mapped with the ARRAY type, as depicted in the below screenshot.

<configuration doc:name="Configuration"> <expression-language autoResolveVariables="false"> <global-functions> def castArray(marks) { org.mule.module.db.internal.resolver.database.StaticDbConfigResolver staticDBConfig = org.mule.RequestContext.getEventContext().getMuleContext().getRegistry().lookupObject("Oracle_Configuration"); org.mule.api.MuleEvent muleEvent = org.mule.RequestContext.getEvent(); org.mule.module.db.internal.domain.database.GenericDbConfig genericDBConfig = (org.mule.module.db.internal.domain.database.GenericDbConfig)staticDBConfig.resolve(muleEvent); oracle.jdbc.OracleConnection con = (oracle.jdbc.OracleConnection)genericDBConfig.getDataSource().getConnection(); oracle.sql.ARRAY lMarks = con.createARRAY("SCOTT.NUMBER_T", marks); con.close(); return lMarks; } </global-functions> </expression-language> </configuration>

Note: The global function depicted above uses Mule internals and therefore becomes risky if this code is migrated in higher versions of Mule and there are changes made in the specifications of Mule internals.

Page 6: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 5

So, how can we solve this issue? This issue can be solved by configuring the database using pooled data source, as described in the next section.

3. Oracle Configuration with Data Source <db:oracle-config name="Oracle_Configuration" doc:name="Oracle Configuration" dataSource-ref="studentDataSource"> </db:oracle-config> <spring:beans> <spring:bean id="studentDataSource" name="studentDataSource" class="oracle.jdbc.pool.OracleDataSource"> <spring:property name="password" value="tiger"/> <spring:property name="URL" value="jdbc:oracle:thin:@localhost:1521:XE"/> <spring:property name="user" value="scott"/> <spring:property name="dataSourceName" value="ds"/> </spring:bean> </spring:beans> 4. Global Function Configuration (Stable Version) <configuration doc:name="Configuration"> <expression-language autoResolveVariables="false"> <global-functions> def castArray(marks) { javax.sql.DataSource ds = (javax.sql.DataSource)org.mule.RequestContext.getEventContext().getMuleContext().getRegistry().lookupObject("studentDataSource"); oracle.jdbc.OracleConnection con = (oracle.jdbc.OracleConnection)ds.getConnection(); oracle.sql.ARRAY lMarks=con.createARRAY("SCOTT.NUMBER_T", marks); con.close(); return lMarks; } </global-functions> </expression-language> </configuration>

Flow Elements Configuration 1. File Inbound Endpoint

The following properties pertaining to the General Tab should be set: Property Value Display Name Students CSV File Path C:\Users\pbgho\AnypointStudio\workspace\Passing_Java_Arrays_in_Oracle_Stored_Procedure_from_Mule_ESB_Flow\src\main\resources\input

Page 7: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 6

Move to Pattern Student.csv Move to Directory C:\Users\pbgho\AnypointStudio\workspace\Passing_Java_Arrays_in_Oracle_Stored_Procedure_from_Mule_ESB_Flow\src\main\resources\output

2. Transform Message Component

This component is used to transform the CSV file contents into a collection of Java objects of type student.Student class. Each record of the CSV file is transformed into a Java object of type student.Student class. The below figure depicts the transformation:

Set the metadata for Input and Output sections of Transform Message component. The metadata for Input section is derived from the CSV file. The metadata for Output section is created with a Java Collection of student.Student class. The structure of student.Student.java class is defined below:

package student; public class Student implements java.io.Serializable { private static final long serialVersionUID = 7940976767680103084L; private String name; private float[] marks; public Student() { super(); }

Page 8: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 7

public String getName() { return name; } public void setName(String name) { this.name = name; } public float[] getMarks() { return marks; } public void setMarks(float[] marks) { this.marks = marks; } } 3. For Each Scope

The For Each scope is used for wrapping a Database Connector so that the stored procedure can be invoked for each student data and subsequently stored in the underlying Oracle database. The following figure depicts the properties to be defined for For Each scope:

The following table shows the properties to be set for ‘For Each’ scope:

Property Value Description Collection #[payload] Depicts the collection object transformed by the Transform Message component. The payload is List<Student>.

a. Database Connector

The properties depicted in the figure below should be set for the Database Connector.

Page 9: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 8

The table below shows the properties to be set for the Database Connector:

Property Value Description Connector configuration

Oracle_Configuration Refer ‘Oracle Configuration’ bullet point under ‘Global Configurable Elements’ section. Operation Stored Procedure Select Stored Procedure from the drop down list. Type Parameterized Select Parameterized from the drop down list. Parameterized query

{call scott.insert_students(:sname, :marks)} Oracle stored procedure invocation statement with two

Page 10: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 9

parameters. Parameters Parameter Name

IN/OUT Value Type Setting the parameters for the Oracle stored procedure. sname IN #[payload.getName()] VARCHAR marks IN #[castArray(payload.getMarks())] ARRAY

The point to be noted here is regarding the parameter section of the Database Connector, as depicted above where the value for marks parameter is defined as the global function call castArray which takes the Java array marks defined in student.Student class as the parameter and creates oracle.sql.ARRAY object.

Conclusion To conclude this tutorial, let us execute this application and check the result.

1. The below figure shows that the STUDENTS table is empty.

2. Run the application with the following records in the student.csv file using the Global

Function ‘castArray’ written with Mule internals. Name,Eng,Phy,Chem,Math Praveen Kumar,96.0,77.0,85.0,89.0

Page 11: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 10

Kamlesh Yadav,78.0,65.0,56.0,45.0 Manoj Kumar,89.0,77.0,86.0,98.0 Rajesh Mistry,87.0,56.0,45.0,89.0 Pankaj Sinha,77.0,89.0,76,0,56.0 Sumit Kumar,89.0,67.0,75.0,66.0

3. The below screen shot justifies that the above CSV records were inserted into the database table Students.

4. Run the application with the following records in the student.csv file using the Global

Function ‘castArray’ written without Mule internals. Name,Eng,Phy,Chem,Math Navneet Singh,46.0,67.0,75.0,89.0 Pratap Sinha,58.0,75.0,96.0,55.0 Archana Yadav,69.0,87.0,56.0,88.0 Utpal Ghosh,67.0,86.0,95.0,49.0 Lalit Kumar,67.0,79.0,66,0,86.0 Chaman Pandit,59.0,77.0,65.0,86.0

5. The below screen shot justifies that the above CSV records were inserted into the database table Students.

Page 12: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 11

Complete XML Code <?xml version="1.0" encoding="UTF-8"?> <mule xmlns:mulexml="http://www.mulesoft.org/schema/mule/xml" xmlns:tracking="http://www.mulesoft.org/schema/mule/ee/tracking" xmlns:scripting="http://www.mulesoft.org/schema/mule/scripting" xmlns:db="http://www.mulesoft.org/schema/mule/db" xmlns:file="http://www.mulesoft.org/schema/mule/file" xmlns:json="http://www.mulesoft.org/schema/mule/json" xmlns:dw="http://www.mulesoft.org/schema/mule/ee/dw" xmlns:metadata="http://www.mulesoft.org/schema/mule/metadata" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:spring="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

Page 13: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 12

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd http://www.mulesoft.org/schema/mule/ee/dw http://www.mulesoft.org/schema/mule/ee/dw/current/dw.xsd http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd http://www.mulesoft.org/schema/mule/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd http://www.mulesoft.org/schema/mule/file http://www.mulesoft.org/schema/mule/file/current/mule-file.xsd http://www.mulesoft.org/schema/mule/db http://www.mulesoft.org/schema/mule/db/current/mule-db.xsd http://www.mulesoft.org/schema/mule/scripting http://www.mulesoft.org/schema/mule/scripting/current/mule-scripting.xsd http://www.mulesoft.org/schema/mule/ee/tracking http://www.mulesoft.org/schema/mule/ee/tracking/current/mule-tracking-ee.xsd http://www.mulesoft.org/schema/mule/xml http://www.mulesoft.org/schema/mule/xml/current/mule-xml.xsd"> <db:oracle-config name="Oracle_Configuration" doc:name="Oracle Configuration" dataSource-ref="studentDataSource"> </db:oracle-config> <configuration doc:name="Configuration"> <expression-language autoResolveVariables="false"> <global-functions> def castArray(marks) { //org.mule.module.db.internal.resolver.database.StaticDbConfigResolver staticDBConfig = org.mule.RequestContext.getEventContext().getMuleContext().getRegistry().lookupObject("Oracle_Configuration"); //org.mule.api.MuleEvent muleEvent = org.mule.RequestContext.getEvent(); //org.mule.module.db.internal.domain.database.GenericDbConfig genericDBConfig = (org.mule.module.db.internal.domain.database.GenericDbConfig) staticDBConfig.resolve(muleEvent); //oracle.jdbc.OracleConnection con = (oracle.jdbc.OracleConnection)genericDBConfig.getDataSource().getConnection(); javax.sql.DataSource ds = (javax.sql.DataSource)org.mule.RequestContext.getEventContext().getMuleContext().getRegistry().lookupObject("studentDataSource"); oracle.jdbc.OracleConnection con = (oracle.jdbc.OracleConnection)ds.getConnection(); oracle.sql.ARRAY lMarks = con.createARRAY("SCOTT.NUMBER_T", marks); con.close(); return lMarks; } </global-functions> </expression-language> </configuration> <spring:beans> <spring:bean id="studentDataSource" name="studentDataSource" class="oracle.jdbc.pool.OracleDataSource">

Page 14: Passing java arrays in oracle stored procedure from mule esb flow

PAGE 13

<spring:property name="password" value="tiger"/> <spring:property name="URL" value="jdbc:oracle:thin:@localhost:1521:XE"/> <spring:property name="user" value="scott"/> <spring:property name="dataSourceName" value="ds"/> </spring:bean> </spring:beans> <flow name="PassingJavaArraysInOracleStoredProcedureFlow"> <file:inbound-endpoint path="C:\Users\pbgho\AnypointStudio\workspace\Passing_Java_Arrays_in_Oracle_Stored_Procedure_from_Mule_ESB_Flow\src\main\resources\input" moveToPattern="student.csv" moveToDirectory="C:\Users\pbgho\AnypointStudio\workspace\Passing_Java_Arrays_in_Oracle_Stored_Procedure_from_Mule_ESB_Flow\src\main\resources\output" responseTimeout="10000" metadata:id="46b599ed-e0e6-46cc-93c9-0564bb8aff2f" doc:name="Students CSV File"/> <dw:transform-message metadata:id="eee60e7a-a87d-483b-bd84-1568e4ccda39" doc:name="Transform Message"> <dw:set-payload><![CDATA[%dw 1.0 %output application/java --- payload map ((payload01 , indexOfPayload01) -> { name: payload01.Name, marks: [payload01.Eng, payload01.Phy, payload01.Chem, payload01.Math] } as :object {class: "student.Student"})]]></dw:set-payload> </dw:transform-message> <foreach doc:name="For Each" collection="#[payload]"> <db:stored-procedure config-ref="Oracle_Configuration" doc:name="Oracle Database 11.2g"> <db:parameterized-query><![CDATA[{call scott.insert_students(:sname, :marks)}]]></db:parameterized-query> <db:in-param name="sname" type="VARCHAR" value="#[payload.getName()]"/> <db:in-param name="marks" type="ARRAY" value="#[castArray(payload.getMarks())]"/> </db:stored-procedure> </foreach> <logger message="Completed" level="INFO" doc:name="Logger"/> </flow> </mule> I hope this tutorial will help those who were posting issues regarding stored procedure invocation issues in forums. You should also try for INOUT and OUT parameter types.