I am new to Camel and trying to find a way to pass object to method in SetHeader.
but i am getting an error,
org.apache.camel.language.bean.RuntimeBeanExpressionException: Failed to invoke method: getCustProcessDir('${header.CUST}') on null due to: org.apache.camel.component.bean.ParameterBindingException: Error during parameter binding on method: public java.lang.String CustDao.getCustProcessDir(Cust) at parameter #0 with type: class Cust with value type: class java.lang.String and value: Cust#199b87b5
at org.apache.camel.language.bean.BeanExpression.invokeOgnlMethod(BeanExpression.java:430) ~[camel-bean-3.3.0.jar:3.3.0]
at org.apache.camel.language.bean.BeanExpression.evaluate(BeanExpression.java:164) ~[camel-bean-3.3.0.jar:3.3.0]
codes:
fromF("file:C:/Users/a/Documents/Development/input/"
+ "?recursive=false&noop=true&delay=20000&readLockLoggingLevel=WARN&shuffle=true"
+ "&readLock=idempotent&idempotentRepository=#fileRepo&readLockRemoveOnCommit=true&readLockRemoveOnRollback=true&delete=true&moveFailed=%s"
, "C:/Users/a/Documents/Development/rejected/")
.routeId("fileMoveRoute")
.process(exchange -> {
exchange.getMessage().setHeader("Application_ID", appInfo.getInstanceId());
})
.threads(appInfo.getThreadCount())
.setHeader("CUST", method(CustDao.class, "getInboundCustWithfile('${header.CamelFilePath}')"))
.setHeader("PROCESS_DIR", method(CustDao.class, "getCustProcessDir('${header.CUST}')"))
...
public String getCustProcessDir(Cust cust) {
return appInfo.getDir() + cust.getCustprofid() + "/hold/";
}
public class Cust {
private int custid;
private String custprofid;
...
}
first setHeader("CUST"..) works and i believe that Header("CUST") has returned object values.
but I am not sure how it is stored in Camel. I've tried to find them from variables window during debug but was not able to find them. too many variables to look into... Where can i find this Header values during debug?
and how can i pass object values to the method?
.setHeader("PROCESS_DIR", method(CustDao.class, "getCustProcessDir('${header.CUST}')"))
or is there a better way to pass/handle object during routing?
Thanks,
I guess the single quotes around expressions like ${header.CUST} are the problem because the RuntimeBeanExpressionException complains that it receives the String Cust#199b87b5 instead of a Cust object.
Have a look at the Camel docs for Bean binding. There are no single quotes around method parameter expressions.
About the storage of header variables: they are stored on the Message object of Camel.
Exchange -> Message -> Headers
Exchange -> ExchangeProperties
Related
I have a form that contains multiple radio inputs and one textarea input that I send using axios from a ReactJs client. The request looks like this:
axios.post("/wellbeing/" + params.wellbeingSurveyType, { formAnswersJson: formAnswers })
.then(res => {
// logic
})
.catch(err => {
// logic
})
The 'formAnswers' object looks like this:
I then receive the request from a Spring controller that looks like the following:
#PostMapping("{wellbeingSurveyType}")
public WellbeingSurveySubmission submitSurvey(
#PathVariable WellbeingSurveyType wellbeingSurveyType,
#RequestBody String formAnswersJson) throws JsonProcessingException {
var result = new ObjectMapper().readValue(formAnswersJson, HashMap.class);
return new WellbeingSurveySubmission(); //ignore this
}
When I call the 'toString()' method on the result object it seems to correctly print out the map values:
But when I try to actually operate on the object (which is parsed as a LinkedHashMap) I cannot access the keys or values:
When I try to open up the object using the debugging tool it seems to store a reference to itself as a value:
The result I want is simply a Map<String, String> that represents the JSON but I am unsure why this behavior is happening.
Any help or tips on how to do this in a better way would be greatly appreciated, thank you.
Alright the best way I found to make this work was to deconstruct the JSON object in the axios post request like so:
axios.post("/wellbeing/" + params.wellbeingSurveyType, { ...formAnswers })
.then(res => {
// logic
})
.catch(err => {
// logic
})
Works better as if I just pass the formAnswers object it unnecessarily wraps the object i.e. A hashmap that contains a single key-value pair 'formAnswers'.
Although as The Frozen One mentioned, it would be better to define a dedicated form object and take it as a param in the spring controller.
If you pass a JavaScript object as the 2nd parameter to the axios.post() function, Axios will automatically serialize the object to JSON for you.
So, with this line of code :
axios.post("/wellbeing/" + params.wellbeingSurveyType, { formAnswersJson: formAnswers })
You are basically sending object with key fromAnswersJson and value fromAnswers to your rest controller and Spring will deserilize it like a Map with key fromAnswersJson and value fromAnswers
To get what you want just send your request like this :
axios.post("/wellbeing/" + params.wellbeingSurveyType, formAnswers )
It Seems like the conversion from String to Map in java does not go smoothly from what I see in your printscreen.
Personally, I do not work like that when I handle requests. I create a dto object and give that in the controller as input. The fact that you have variables that the name is a number make that a bit more complicated since java cannot accept that as a valid variable name, but probably (did not test it) can be overcome by using #JsonProperty. So my solution would be the following
#Getter
#Setter
public class MyRequestDto {
#JsonProperty("user-comment")
private String userComment;
#JsonProperty("0")
private String zero;
#JsonProperty("1")
private String one;
#JsonProperty("2")
private String two;
...
}
I added lombok getters and setters ofcourse you can add your own if you don't use lombok.
Then replace the input in your controller
#PostMapping("{wellbeingSurveyType}")
public WellbeingSurveySubmission submitSurvey(
#PathVariable WellbeingSurveyType wellbeingSurveyType,
#RequestBody MyRequestDto request) throws JsonProcessingException {
request.getUserComment()
return new WellbeingSurveySubmission(); //ignore this
}
I'm seeing a very bizarre issue with iBatis when trying to read a property from a Java map using isEqual, but not with other iBatis operators. For example it is able to read the map properties fine when using isNotNull and iterate. The xml:
<isNotNull property="filterCriteria.account">
AND
id
<isEqual property="filterCriteria.account.meetsCriteria" compareValue="false">
NOT
</isEqual>
IN
(SELECT DISTINCT id
FROM account
WHERE some other criteria....
)
</isNotNull>
The 2 java classes we're using here:
public class SearchProfile {
private Map<String, SearchProfileCriteria> filterCriteria;
public SAOSearchProfile() {
filterCriteria = new HashMap<>();
}
public Map<String, SAOSearchProfileCriteria> getFilterCriteria() {
return filterCriteria;
}
public void setFilterCriteria(Map<String, SAOSearchProfileCriteriaBase> filterCriteria) {
this.filterCriteria = filterCriteria;
}
}
Above is the container object that is passed to iBatis for the querying, and below is the criteria object that will be the value of the map. In this example it is keyed with the String "account"
public class SearchProfileCriteria {
boolean meetsCriteria;
public String getCriteriaAsString() {
return StringUtils.getStringValueFromBoolean(meetsCriteria);
}
public boolean isMeetsCriteria() {
return meetsCriteria;
}
public void setMeetsCriteria(boolean meetsCriteria) {
this.meetsCriteria = meetsCriteria;
}
public String getSQLString(){
return meetsCriteria ? "" : "NOT";
}
}
And the exception:
Cause: com.ibatis.common.beans.ProbeException: There is no READABLE property named 'account' in class 'java.util.Map'; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException:
The getSQLString() method was my half baked attempt at a work around, the String gets escaped in the query and throws a syntax error.
When I remove the <isEqual> block the query executes find, which indicates it is able to read the "account" key when checking the to see if it is null. As I mentioned above, we're also able to use the map keys in <iterate> tags without issue. It seems <isEqual> and <isNotEqual> are the only tags causing issues. Does anyone have experience with this or know what may be going on?
Beware: Using isNotNull, isEqual, iterate is iBatis, they don't exist anymore in Mybatis, so referencing to Mybatis indifferently is confusing.
Reference documentation.
For your issue, how does it behave if replacing Map with a class (property will be known at compile time)?
Or try using <isPropertyAvailable>.
The work around could work with right syntax: $ instead of #: $filterCriteria.account.SQLString$ instead of #filterCriteria.account.SQLString#, then the value is just concatenated instead of bound as parameter.
I have an application that uses Apache Camel with Active MQ. In my RouteBuilder class where I configure the routes, it is possible that a parameter may be missing from the in body. In that case, I would like to give it a default value.
Here is what I have now:
#Override
public void configure() throws Exception {
...
String invocation = "aMethod( ${body[context]}, ${body[aParam]}, ${body[param2]} )";
from(url).routeId(url).bean(bean, invocation);
}
In my case, param2 is a boolean which I would like to send forward to aMethod as false in case it doesn't exist. How can I do that?
I saw something here: http://camel.apache.org/simple.html#Simple-OGNLexpressionsupport where it says "You can also use the null safe operator (?.) to avoid NPE if for example the body does NOT have an address", but I can't figure how to write the invocation so I don't have an error in the specified case.
I believe you are looking to use a conditional similar to this:
from("")
.choice()
.when(header("myHeader").isNull())
.setHeader("myHeader").simple("false")
.end() //This might be endChoice can't remember syntax exactly
.bean(myBean, myMethod);
..revised for Object in the body
from("")
.processor( new Processor(Exchange exchange) {
MyObject obj = exchange.getIn().getBody(MyObject.class);
Boolean b = obj.getParam2();
if(b == null)
obj.setParam2(Boolean.FALSE);
}
.bean(myBean, myMethod);
NOTE: If your actual java class uses 'boolean' and not the 'Boolean' class wrapper, then all booleans when initialized are by default set to false. So if you have an object in your body and nobody has the the boolean it will default to false.
I know it's kind of the wrong thing to do, but I'm dealing with a legacy codebase that has NULLS when it means empty strings and vice versa.
I can't immediately see how it is possible, but is it possible to get (or modifiy dapper so it will) return an empty string instead of a null string when mapping back from the database.
Dapper doesn't call any setter when it sees a null, so options might include:
set the default value to "" in the constructor
check for null in the accessor
So:
public class SomeDto
{
public SomeDto()
{
Name = "";
}
public string Name {get;set;}
}
or:
public class SomeDto
{
private string name;
public string Name { get {return name ?? "";} set {name = value;} }
}
However, this only applies to reading values; I can't think of a nice way to get dapper to turn "" into null when passing the dto in as the parameter object; options include:
creating an anon-type, substituting "" to null (perhaps write a string NullIfBlank(this string s) extension method)
having a shim property on the type that returns null in place of "", and have your database query bind to #NameOrNull rather than #Name
You can control this with your queries, for example:
public class Data
{
public string Foo { get; set; }
}
var result = conn.Query<Data>("select Foo = coalesce(Foo, '') from MyTable");
So in the above example, coalesce will return an empty string when Foo is null.
In short: depending how you load the data to the dapper you may get two different scenarios.
First: Turn up your data provider layer, for example like in this post - How to return null from a Dapper query rather than default(T)?.
Second way to try: you may modify your GetTypeDeserializer like in the following post - Change Dapper so that it maps a database null value to double.NaN
Third and the last: it is my friendly advice to work on your previous questions acceptance rate. In this way you may increase chances of replies for your questions.
Hope all this will help.
I tend to use a global extension method on string called ConvertNull() which converts any null values to an empty string. You can then call this anywhere without your code looking cluttered. If you're using this directly on an aspx page, just make sure you've imported the namespace of the extension methods and then the method will be available to you:
namespace ExtensionMethods
{
using System;
public static class StringExtensionsClass
{
/// <summary>Converts null strings to empty strings</summary>
/// <param name="s">Input string</param>
/// <returns>Original string, or empty string if original string was null</returns>
public static string ConvertNull(this string s)
{
return s ?? "";
}
}
}
Then call this on an instance of a string.
Usage:
myStringInstance.ConvertNull().Replace("\r\n", "<br />");
I am trying to pass a Long datatype array to one of my webservivce method.
My Webservice method is looks follows:
public String calculate(Long[] values)
{
//my code here to process the array
}
From the client side i am calling the webservice like follows
Long[] data=new Long[1];
data[0]=1;
proxy = webService.getClient(wsdlURL)
String response=proxy.calculate(data);
But it throwing me the exception javax.xml.bind.JAXBException: class [Ljava.lang.Long; nor any of its super class is known to this context.
Based on googling i understand this one because of JAXB unable to marshall the Long array. But i dont know how to fix it. Any help?
Try using ArrayList() on the client side:
List<Long> data = new ArrayList<Long>();
data.add(new Long(1));
data.add(new Long(5));
proxy = webService.getClient(wsdlURL)
String response=proxy.calculate(data);
Not sure, but maybe BigInteger would work for you? That seems to be the default mapping from xml integer to java type.