I'm calling an Oracle Stored Procedure using Apache Camel's SQL Stored Procedure Component.
The Stored Procedure that I'm calling has several OUT parameters which are all returned in the BODY as a String, for example:
{param1=0, param2=-, param3=<?xmlversion="1.0"?><Client><Item><PHONE>1234567890</PHONE></Item></Client>}
Currently I'm tokenizing/parsing the BODY to be able to extract the parameters. This works but it's not efficient (and error prone).
My code looks like this:
...
<bean id="od" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="driver">
<bean class="oracle.jdbc.OracleDriver"/>
</property>
<property name="url" value="${connURL}"/>
</bean>
...
<camel:to uri="sql-stored:PACKAGE.STORED_PROCEDURE(VARCHAR ${exchangeProperty.param0},OUT VARCHAR param1,OUT VARCHAR param2,OUT VARCHAR param3,OUT VARCHAR param4)?dataSource=od">
...
<!-- I tried this but "abc" always has no value -->
<camel:setHeader headerName="abc">
<camel:description layoutX="280" layoutY="20" layoutWidth="120" layoutHeight="120"/>
<camel:simple>$simple{header.param3}</camel:simple>
</camel:setHeader>
<!-- This is how I parse the BODY (inefficient!) -->
<camel:groovy><![CDATA[
def outParams = [
param1: "NO_VALUE",
param2: "NO_VALUE",
param3: "NO_VALUE",
param4: "NO_VALUE"
]
def retERR = "yes"
def t = ""
def bd = exchange.getIn().getBody(String.class)
try {
def varList = bd.replaceAll("\n","").replaceAll("^\\{","").replaceAll("\\}\$","")
varList = varList.tokenize(',')
for (keyVal in varList) {
keyVal = keyVal.trim()
i = keyVal.indexOf('=')
outParams[keyVal.take(i)] = keyVal.substring(i+1)
}
retERR = "no"
} catch(all) {
retERR = "yes"
}
exchange.setProperty("outParams",outParams)
exchange.setProperty("retERR",retERR)
]]>
</camel:groovy>
...
<camel:to uri="bean:varpop?method=MYBEAN('outParams,retERR,param3,abc')">
<camel:description layoutX="20" layoutY="470" layoutWidth="120" layoutHeight="120"/>
</camel:to>
Is there a direct way of accessing param1, param2, etc... directly without having to parse the BODY?
sql-stored-component producer already returns Map to body. Throw away all your parsing logic, because the String, you are parsing, is just toString() representation of Map.
...
<camel:to uri="sql-stored:PACKAGE.STORED_PROCEDURE(VARCHAR ${exchangeProperty.param0},OUT VARCHAR param1,OUT VARCHAR param2,OUT VARCHAR param3,OUT VARCHAR param4)?dataSource=od"/>
<!-- Variant 1: Pass Map to MYBEAN method -->
<camel:to uri="bean:varpop?method=MYBEAN(${body})"/>
<!-- Variant 2: Get params from Map and pass it as arguments -->
<camel:to uri="bean:varpop?method=MYBEAN(${body[param1]},${body[param2]},${body[param3]},${body[param4]})"/>
And then use it in your varpop bean:
public class Varpop{
// Variant 1: Map as argument
public void MYBEAN(Map<String, String> procedureResult){
System.out.println(procedureResult.get("param1"));
System.out.println(procedureResult.get("param2"));
//...
}
// Variant 2: Multiple arguments
public void MYBEAN(String param1, String param2, String param3, String param4){
System.out.println(param1);
System.out.println(param2);
//...
}
}
Related
We have a independent bundle creating factory parts based on a few header variables [direct-vm:createPartService].
My application needs loop through a list of parts and call this bundle repetitively. My partBean.update method just retrieve part info from a part list, setup the header variables and then invoke the createPartService.
I uses the following doWhile loop. I find when createPartService return status is ERROR, the following loop works perfectly. The workflow exits from the loop and goes to exception handling section. But, when the createPartService return status never error, i.e., every createPartService is SUCCESS, the while loop never stop.
<loop doWhile="true">
<simple>${header.STATUS} == 'SUCCESS'</simple>
<bean ref="partBean" method="update"/>
<to uri="direct-vm:createPartService"/>
<choice>
<when>
<simple>${header.STATUS} == 'ERROR'</simple>
<throwException exceptionType="java.lang.Exception message="${header.ERROR_MESSAGE}"/>
<to uri="mock:endPartsError"/>
</when>
<otherwise>
<log message="### update part succeeded" loggingLevel="DEBUG"/>
</otherwise>
</choice>
</loop>
Then, I tried and another condition,
<simple>${header.STATUS} == 'SUCCESS' && ${exchangeProperty[CamelLoopIndex]} < ${headers.PART_COUNT}</simple>
Camel seems does not accept logic 'and'. The above evaluation always returns false. I also tried replace '&&' with 'and', or the corresponding html entity. There is no difference. Any suggestion on looping through a list and exit gracefully when there is no error?
You did not set your loop terminate condition properly.
Use a bean to control your looping terminate condition
Camel Spring XML
<loop doWhile="true">
<simple resultType="java.lang.Boolean">${bean:partBean?method=isContinueLoop}</simple>
... do stuff here ...
</loop>
Java Bean
public Boolean isContinueLoop(Exchange exchange) throws Exception {
// System.out.println("LoopIndex: " + exchange.getProperty(Exchange.LOOP_INDEX));
Boolean result = false;
String status = exchange.getIn().getHeader("STATUS", String.class);
Integer loopIndex = exchange.getProperty(Exchange.LOOP_INDEX, -1, Integer.class);
int maxSize = exchange.getIn().getHeader("PART_COUNT", Integer.class);
if (status != null && status.equals("SUCCESS") && loopIndex < maxSize) {
result = Boolean.TRUE;
}
return result;
}
The main problem to the condition ${header.STATUS} == 'SUCCESS' && ${exchangeProperty[CamelLoopIndex]} < ${headers.PART_COUNT} is not on the AND operator, but the value of ${exchangeProperty[CamelLoopIndex]}.
If you uncomment the system print in the java bean, you could observe the CamelLoopIndex's value is change as : null -> 0 -> 1 -> 2 -> ..., the null value is the root problem to make your condition return false. To avoid this in Camel Spring XML Simple language, you need a null check on CamelLoopIndex with a OR operator.
In this sense, you will need both a AND operator and a OR operator. However, this is unsupported by simple language currently as state in Simple language page Section 'Using and / or'.
<split> offers an excellent solution for my use case. In the following code sample, the findParts method returns a list of Part object. The update method setups header variables needed for web service call (createPartService).
<route id="WebServiceUpdateParts">
<from uri="direct:updateParts"/>
<split stopOnException="true">
<method ref="partBean" method="findParts"/>
<bean ref="partBean" method="update"/>
<to uri="direct-vm:createPartService"/>
</split>
<to uri="mock:endUpdateParts"/>
</route>
If you wanna stop the loop after specific round, you can use this:
from("direct:test").setProperty("CamelLoopIndex",constant(0)).loopDoWhile(simple("${exchangeProperty.CamelLoopIndex} <= 3")).log("=====${exchangeProperty.CamelLoopIndex}");
Notice that you must declare CamelLoopIndex property explicitly before the loop, otherwise Camel will not understand the contiditon when starts up the route, because CamelLoopIndex property is created after the loop begins.
Stored Procedure call using WebApi EntityFrameWorkCore 1.1 gets and object not the integer results
I have the following user defined procedure to call a calculated data from database with 2 parameters to return an integer (Count(*))
USE [QIIS2]
GO
PROCEDURE [dbo].[sp.GetCansTotals]
#hospitalId int
AS
BEGIN
SET NOCOUNT ON;
SELECT COUNT(*) AS TotalCancelled
FROM Cans
WHERE hospitalId = #hospitalId;
END
The repository to call the procedure:
public async Task GetCansTotals(int hospitalId)
{
using (appContext)
{
var hospitalid = new SqlParameter("#hospitalId", 1);
var cans = appContext.Cans.FromSql("Exec GetCansTotals #hospitalId", hospitalId);
}
}
and the controller:
[HttpGet("byParams")]
public IActionResult GetCanTotal(int hospitalId)
{
var res = _unitOfWork.Cans.GetCansTotals(hospitalId);
return Ok(res);
}
When passing the request with postman:
http://localhost:56963/api/cansdatas/byParams?hospitalId=2
I get an object rather than the results of COUNT(*)
{
"result": {},
"id": 1,
"exception": null,
"status": 5,
"isCanceled": false,
"isCompleted": true,
"creationOptions": 0,
"asyncState": null,
"isFaulted": false
}
Can you help please?
Couple of issues with your code. Here's what you can do -
You are returning a scalar value (count) from the stored procedure and calling it on the DbSet via the FromSql method. This method should be used when the stored procedure returns a result set that maps to the properties of the 'Can' type (or whatever type the 'Cans' property is returning in the DbSet). This will not work.
It seems that to execute stored procedures that return a scalar value you still need to fall back on plain ADO.NET . You can add an extension method on your DbContext or in your repository to do this. Check this answer for more details - Entity Framework 7 FromSql stored procedure return value
You could also return the count without a stored procedure but using LINQ.
Also change the return type of the method to Task and make it async.
public async Task<int> GetCansTotals(int hospitalId)
{
using (appContext)
{
return appContext.Cans.CountAsync(c => c.HospitalId == hospitalId);
}
}
Consider marking your controller method also as async and call your repository method using await. Also you would need to change the return type of the controller method to Task.
[HttpGet("byParams")]
public async Task<IActionResult> GetCanTotal(int hospitalId)
{
var res = await this._unitOfWork.GetCansTotals(hostpitalId);
return Ok(res);
}
I'm trying to call a SalesForce web service via SSIS, and I am trying to retrieve the value of the sessionID node.
Here is the XML:
<?xml version="1.0" encoding="utf-16"?>
<LoginResult xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<metadataServerUrl xmlns="urn:partner.soap.sforce.com">https://xxxxxx/services/Soap/m/31.0/xxxxxx</metadataServerUrl>
<passwordExpired xmlns="urn:partner.soap.sforce.com">false</passwordExpired>
<sandbox xmlns="urn:partner.soap.sforce.com">true</sandbox>
<serverUrl xmlns="urn:partner.soap.sforce.com">https://xxx/services/Soap/u/31.0/xxx</serverUrl>
<sessionId xmlns="urn:partner.soap.sforce.com">xxxxxxxxxx</sessionId>
<userId xmlns="urn:partner.soap.sforce.com">xxx</userId>
<userInfo xmlns="urn:partner.soap.sforce.com">
<accessibilityMode>false</accessibilityMode>
<currencySymbol>$</currencySymbol>
<orgAttachmentFileSizeLimit>5242880</orgAttachmentFileSizeLimit>
<orgDefaultCurrencyIsoCode>USD</orgDefaultCurrencyIsoCode>
<orgDisallowHtmlAttachments>false</orgDisallowHtmlAttachments>
<orgHasPersonAccounts>false</orgHasPersonAccounts>
<organizationId>xxxxxxxx</organizationId>
<organizationMultiCurrency>false</organizationMultiCurrency>
<organizationName>xxxxx</organizationName>
<profileId>xxxxx</profileId>
<roleId xsi:nil="true" />
<sessionSecondsValid>7200</sessionSecondsValid>
<userDefaultCurrencyIsoCode xsi:nil="true" />
<userEmail>xxxxxxx</userEmail>
<userFullName>xxxxx</userFullName>
<userId>xxxxxxx</userId>
<userLanguage>en_US</userLanguage>
<userLocale>en_US</userLocale>
<userName>xxxxxxx</userName>
<userTimeZone>America/New_York</userTimeZone>
<userType>Standard</userType>
<userUiSkin>Theme3</userUiSkin>
</userInfo>
</LoginResult>
I successfully tested this expression via http://www.xpathtester.com/xpath.
Edit:
Trying a script task now, and still I'm not finding the exact right combination to select this information. There are multiple namespaces in the XML, and the below code returns 0 nodes. Quite frustrating!
public void Main()
{
string loginResult;
string sessionID;
loginResult = Dts.Variables["User::loginResult"].Value.ToString();
XmlDocument doc = new XmlDocument();
doc.LoadXml(loginResult);
var xmlnsManager = new System.Xml.XmlNamespaceManager(doc.NameTable);
//xmlnsManager.AddNamespace("t1", "http://www.w3.org/2001/XMLSchema-instance");
xmlnsManager.AddNamespace("ns", "urn:partner.soap.sforce.com");
XmlNodeList list = doc.SelectNodes("/LoginResult/ns:sessionID", xmlnsManager);
for (int i = 0; i < list.Count; i++)
{
sessionID = list[i].Value;
}
Dts.TaskResult = (int)ScriptResults.Success;
}
}
A correct expression to find the sessionId element is:
//*[local-name() = 'sessionId']/text()
In your input XML, the element you'd like to find:
<sessionId xmlns="urn:partner.soap.sforce.com">xxxxxxxxxx</sessionId>
does not have a prefix - it is in a default namespace that does not require elements to be prefixed.
Therefore, a possible explanation is that an expression like
//*:sessionId
only finds elements that are prefixed in the input XML. That should not be a problem but as far as I can see, SSIS is known for problems with namespaced XML (see e.g. here or here).
As far as the XPath specification is concerned, an expression like //*:root should be able to find an element like
<root xmlns="www.example.com"/>
EDIT: Apparently, you have changed the question alltogether - now there is another problem: The outermost element LoginResult is in no namespace at all, not in the xsi: namespace:
<LoginResult xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
The schema instance namespace just happens to be declared on this element, but it is not used there. So, change your code to:
var xmlnsManager = new System.Xml.XmlNamespaceManager(doc.NameTable);
xmlnsManager.AddNamespace("ns", "urn:partner.soap.sforce.com");
XmlNodeList list = doc.SelectNodes("/LoginResult/ns:sessionId", xmlnsManager);
Does anyone know how to extract parameter from camel URI?
I have a route defined like this
from("SOME_URI")
.to("SOME_URI")
.to("bean:myBean?method=myMethod&myParameter1=val1&myParameter2=val2")
I want to extract parameter1 and parameter2 in "myMethod" like this (I'm implementing camel in Grails)
def myMethod(def inBody, Exchange exchange){
String parameter1 = extractParameter('myParameter1')
String parameter2 = extractParameter('myParameter2')
...//rest of code
return something
}
Thank's in advance!
Main Answer
You can get what you're looking for out of the exchange:
exchange.getFromEndpoint()
Will return the Endpoint defined by "SOME_URI" and:
exchange.getFromEndpoint().getEndpointUri()
will return the String value of "SOME_URI"
Meaning your code could become:
def myMethod(def inBody, Exchange exchange){
def uri = exchange?.fromEndpoint?.endpointUri
if(uri) {
String parameter1 = extractParameter(uri, 'myParameter1')
String parameter2 = extractParameter(uri, 'myParameter2')
//...rest of code
}
return something
}
/*
* do any kind of processing you want here to manipulate the string
* and return the parameter. This code should work just fine in grails
*/
def extractParameter(String uri, String parameterName) {
def m = uri =~ "${parameterName}=([^&]+)"
return m.find() ? m[0][1] : null
}
If a Java equivalent is preferred, this should do the same:
private static String extractParameter(String uri, String parameterName) {
Matcher m = Pattern.compile(parameterName + "=([^&]+)").matcher(uri);
return m.find() ? m.group(1) : null
}
Alternative
Also note that, depending on what exactly you're trying to accomplish, a better approach might be to use the fromF DSL to supply parameters directly to your route. That way, you have the parameters available in code and you don't have to worry about extracting them, afterward.
The code snippet below is taken from the Camel Documentation of FromF.
fromF("file://%s?include=%s", path, pattern).toF("mock:%s", result);
Are val1 and val2 hardcoded values, or should they be some kind of dynamic value, maybe from the message itself?
The Camel bean component allows you to define the binding, and pass in values from the message or fixed values. See more details at: http://camel.apache.org/bean-binding.html
And you would also need to look at the number of parameters in your method signature, and the number of parameters you define in the Camel bean binding uri. They should match up.
If I understand correctly, you are trying to pass parameters into a method that is going to be invoked. The usual way to do this is to modify the Exchange object as it's flowing through the route.
from("SOME_URI")
.to("SOME_URI")
.setHeader("myParameter1", constant("val1"))
.setHeader("myParameter2", constant("val2"))
.to("bean:myBean?method=myMethod")
In your method, you just access the headers of the Exchange.
def myMethod(Exchange exchange) {
String parameter1 = exchange.getHeader("myParameter1", String.class)
String parameter2 = exchange.getHeader("myParameter2", String.class)
//...rest of code
}
Or if you want to get fancy and use Camel's bean binding,
def myMethod(Exchange exchange,
#Header("myParameter1") String parameter1,
#Header("myParameter2") String parameter2) {
//...rest of code
}
Please remember to vote up if this helps.
I have the following fields:
In the database I have the field property_industry_sector which is a list of comma separated ints, null or empty string.
In the Solr schema configuration I have the same field property_industry_sector of type int and multivalued.
My problem is that I have to handle to difference in the DataImportHandler configuration, and my attempt looks like this:
<entity
name="property_industry_sector_extractor"
transformer="script:SplitIndustrySector"
query="
SELECT property_industry_sector
FROM job
WHERE job.id = ${job.id}
">
<field column="property_industry_sector" name="property_industry_sector" />
</entity>
Where the ScriptTransformer has the following definition:
function SplitIndustrySector(row) {
//var logger = java.util.logging.Logger.getLogger("org.apache.solr");
if(row.get('property_industry_sector') !== null) {
if(false === row.get('property_industry_sector').isEmpty()) {
var pieces = row.get('property_industry_sector').split(',');
var arr = new java.util.ArrayList();
for(var i=0, len=pieces.length; i<len; i++) {
arr.add(new java.lang.Integer(pieces[i]));
}
row.put('property_industry_sector', arr);
return row;
}
}
var arr = new java.util.ArrayList();
arr.add(new java.lang.Integer(0));
row.put('property_industry_sector', arr);
return row;
}
The problem is with the general case, when the value is null or empty string, because no matter what the transformer does, I still get the following Exception
property_industry_sector=property_industry_sector(1.0)={[, 0]}}]
java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:493)
at java.lang.Integer.parseInt(Integer.java:514)
at org.apache.solr.schema.TrieField.createField(TrieField.java:374)
at org.apache.solr.schema.SchemaField.createField(SchemaField.java:97)
at org.apache.solr.update.DocumentBuilder.addField(DocumentBuilder.java:203)
at org.apache.solr.update.DocumentBuilder.toDocument(DocumentBuilder.java:276)
at org.apache.solr.update.processor.RunUpdateProcessor.processAdd(RunUpdateProcessorFactory.java:60)
at org.apache.solr.handler.dataimport.SolrWriter.upload(SolrWriter.java:73)
at org.apache.solr.handler.dataimport.DataImportHandler$1.upload(DataImportHandler.java:294)
at org.apache.solr.handler.dataimport.DocBuilder.buildDocument(DocBuilder.java:631)
at org.apache.solr.handler.dataimport.DocBuilder.doFullDump(DocBuilder.java:267)
at org.apache.solr.handler.dataimport.DocBuilder.execute(DocBuilder.java:186)
at org.apache.solr.handler.dataimport.DataImporter.doFullImport(DataImporter.java:353)
at org.apache.solr.handler.dataimport.DataImporter.runCmd(DataImporter.java:411)
at org.apache.solr.handler.dataimport.DataImporter$1.run(DataImporter.java:392)
I do not understand where the empty string comes from (which it tries to convert to Integer) while also being confused by the values it tries to insert above the exception:
property_industry_sector=property_industry_sector(1.0)={[, 0]}}]
I've tried clearing the row prior to the put() call. Return null, or just as with the current example return the row with a single value of 0.
Haven't found a way to work it out, but managed to solve the issue with an alternative solution. Instead of using the ScriptTransformer I was able to achieve the same goal with SQL transformations.
<entity name="industry_sector_hack" query='
SELECT property_industry_sector AS property_industry_sector_ids
FROM job
WHERE id = ${job.id} AND
property_industry_sector IS NOT NULL AND
property_industry_sector <> ""
'>
<entity name="property_industry_sector" query='
SELECT property.id AS property_industry_sector
FROM property
WHERE property.id IN (${industry_sector_hack.property_industry_sector_ids})
'>
<field column="property_industry_sector" name="property_industry_sector" />
</entity>
</entity>