I have arranged for the following Camel route which unmarshals an XML file into a Java object (com.sample.Order):
<route>
<from uri="file:/data/in?fileName=order.xml&noop=true"/>
<unmarshal ref="transform-xml"/>
<to uri="velocity:etc/MailBody.vm"/>
<to uri="file:/data/out"/>
</route>
Here is the MailBody.vm:
#set( $order = $body.get(0).get('com.sample.Order'))
Order status:
- Id: $order.id
- Price: $order.price
Tax: $order.tax
Details:
$order.description
When the Camel route is executed however the generated XML file does not parse the $order fields. Is there anything I miss or maybe this in not working in my camel version (2.15.3) ?
Thanks
Try to modify your template like shown below:
#set( $order = ${body.get(0).get("com.sample.Order")})
Order status:
- Id: ${order.id}
- Price: ${order.price}
Tax: ${order.tax}
Details:
${order.description}
Related
I am a starter for camel. I have a question. I have one route.
<route>
<from uri="file:/test/inBox"/>
<bean method="processingAuditInsert(*, 'RD', 'AA')" ref="fileAuditHandler"/>
<bean method="processingAuditUpdate(*, 'IU')" ref="fileAuditHandler"/>
<toD uri="file:/test/outBox"/>
<bean method="processingAuditUpdate(*, 'CO')" ref="fileAuditHandler"/>
</route>
I applied each parameter for processingAuditUpdate method.
'IU' parameter is correct set to processingAuditUpdate method.
And during second calling method 'processingAuditUpdate', I want to set 'CO' value.
But after processing, this value is the body value of exchange.
I don't understand this situation.
Please help about this situation.
Thank you.
fileAuditHandler.java
#Handler
public void processingAuditUpdate(Exchange ex, String status) {
if (status.equals("IU")) {
// Update Status 'IU'
} else (status.equals("CO")) {
// Update Status 'CO'
} else {
log.error("Update Status value is invalid!!!! ::::: {}", status);
}
}
While trying to use the Apache camel aggregation strategy, I am running into an issue as described below:
My application : Exposed a simple soap service that takes in the names of the departments in an organisation (finance, sales...). Inside my camel route, I route the request accordingly to the department specific routes. In these routes, I query a table, and get the employees for that department.
As a response for the soap service, I want to aggregate all the employees together and send.
Below is my camel Route:
<camelContext id="camel"
xmlns="http://camel.apache.org/schema/spring">
<camel:dataFormats>
<camel:jaxb contextPath="org.example.departments" id="jaxb"/>
</camel:dataFormats>
<route id="simple-route">
<!-- <camel:to id="unmarshallDeps" uri="bean:departmentProcessor"></camel:to> -->
<from id="_from1" uri="cxf:bean:departmentsEndpoint?dataFormat=PAYLOAD&loggingFeatureEnabled=true"/>
<camel:unmarshal id="_unmarshal1" ref="jaxb"/>
<camel:setHeader headerName="departments" id="_setHeader1">
<camel:method method="getDepartmentRoute" ref="depRouter"/>
</camel:setHeader>
<camel:recipientList id="_recipientList1">
<camel:header>departments</camel:header>
</camel:recipientList>
<camel:log id="_log1" message="Body in original cxf after aggregation ******** ${body} and exchange id is ${exchangeId}"/>
</route>
<camel:route id="_route1">
<camel:from id="_from2" uri="direct:finance"/>
<camel:to id="_to1" uri="mySqlComponent:select id,Location,Head,email,create_date from finance"/>
<camel:to id="_to2" pattern="InOut" uri="seda:departmentAggregator"/>
</camel:route>
<camel:route id="_route2">
<camel:from id="_from3" uri="direct:sales"/>
<camel:to id="_to3" uri="mySqlComponent:select id,Location,Head,email,create_date from sales"/>
<camel:to id="_to4" pattern="InOut" uri="seda:departmentAggregator"/>
</camel:route>
<camel:route id="_route3">
<camel:from id="_from4" uri="direct:hr"/>
<camel:to id="_to5" uri="mySqlComponent:select id,Location,Head,email,create_date from hr"/>
<camel:to id="_to6" pattern="InOut" uri="seda:departmentAggregator"/>
</camel:route>
<camel:route id="_route4">
<camel:from id="_from5" uri="seda:departmentAggregator"/>
<camel:aggregate completionSize="2" id="_aggregate1" strategyRef="myAggregator">
<camel:correlationExpression>
<camel:constant>Constant</camel:constant>
</camel:correlationExpression>
<camel:log message="Aggregated Body : ${body} and exchange id is ${exchangeId}"></camel:log>
<!-- <camel:marshal id="_marshal1" ref="jaxb"/> -->
<!-- <camel:setBody id="_setBody1">
<camel:simple>${body}</camel:simple>
</camel:setBody> -->
</camel:aggregate>
<camel:log id="_log4" message="Body outside aggregate : ${body} and exchange id is ${exchangeId}"/>
</camel:route>
</camelContext>
Now, what I notice is that the body when printed inside the aggregate is indeed an aggregated body, containing all the employees, but when I print the body outside the aggregate, it prints the latest exchange and not the aggregated exchange. Below is my aggregation strategy:
public Exchange aggregate(Exchange oldExchange, Exchange newExchange)
{
ArrayList<Map<String, Object>> depList = newExchange.getIn().getBody(ArrayList.class);
int depCount = depList.size();
System.out.println("Department Count is : " + depCount);
if (oldExchange == null) {
for (int i = 0; i < depCount; i++) {
Map<String, Object> row = (Map) depList.get(i);
Department newDepartment = new Department();
newDepartment.setLocation((String) row.get("Location"));
newDepartment.setHead((String) row.get("Head"));
newDepartment.setEmail((String) row.get("email"));
departments.getDepartment().add(newDepartment);
}
newExchange.getIn().setBody(departments);
return newExchange;
} else {
System.out.println("New Exchange: Department Count is : " + depCount);
departments = oldExchange.getIn().getBody(Departments.class);
System.out.println("Aggregate Department count : " + departments.getDepartment().size());
for (int j = 0; j < depCount; j++) {
Map<String, Object> row = (Map) newExchange.getIn().getBody(ArrayList.class).get(j);
Department newDepartment = new Department();
newDepartment.setLocation((String) row.get("Location"));
newDepartment.setHead((String) row.get("Head"));
newDepartment.setEmail((String) row.get("email"));
departments.getDepartment().add(newDepartment);
}
newExchange.getIn().setBody(departments);
oldExchange.getIn().setBody(departments);
}
// System.out.println("exchange is out capable ? : " +
// newExchange.getPattern().isOutCapable());
return oldExchange;
}
Aggregation Strategy is usually something that gets seamlessly embedded into EIPs; you don't have to call it manually, Camel does it for you.
recipientList is precisely one of those EIPs that accepts an aggregation strategy as a parameter.
See example in Camel doc.
Thus your "seda:departmentAggregator" route is in fact not needed (along with all the calls towards it in your "direct:[department]" routes). Instead, its content should be +/- included into the recipientList definition.
I have an ASP.NET WebAPI (v2) controller action that has the following general structure:
[HttpPost]
public HttpResponseMessage Post(UserDTO model)
{
try {
// do something
}
catch (Exception ex)
{
var error = new {
errorMessage = ex.Message,
userId = 123,
// some other simple data
};
return Request.CreateResponse(HttpStatusCode.BadRequest, error);
}
return Request.CreateResponse(HttpStatusCode.OK, model);
}
When I run this on my local development server (IIS Express) and an error is thrown, I get the expected JSON payload back.
{
config: {...},
data: {
errorMessage: "User invalid",
userId: 123,
...
},
status: 400,
statusText: "Bad Request"
}
When I run the same code/data on the remote/production server (IIS 8.5), all I get back is:
{
config: {...},
data: "Bad Request,
status: 400,
statusText: "Bad Request"
}
The custom data payload is lost/stripped away from the response. This appears to be related to the HttpStatusCode used in the Request.CreateResponse() call as if I change HttpStatusCode.BadRequest to HttpStatusCode.OK then the custom data payload is downloaded.
As I test, I tried changing the return to Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState); but the results were the same, i.e. the data was returned as a simple "Bad Request" string.
For reference, the API is being called by an AngularJS $http.post() call.
Why is the change in HttpStatusCode changing the response payload on the production server but not locally? Any help would be much appreciated.
It turns out this was down to the following section in Web.config
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="403" />
<error statusCode="403" responseMode="ExecuteURL"
path="/Error/AccessDenied" />
<remove statusCode="404" />
<error statusCode="404" responseMode="ExecuteURL"
path="/Error/NotFound" />
<remove statusCode="500" />
<error statusCode="500" responseMode="ExecuteURL"
path="/Error/ApplicationError" />
</httpErrors>
</system.webServer>
It was 'odd' because these pages were not being returned by the API call, but with their removal, the API call returned the correct payload - presumably the HttpStatusCode.BadRequest was being intercepted by an error handler somewhere hence losing the original response data?
With these handlers I removed, I resorted to using the Application_Error handler in Global.asax as described by ubik404 here.
There may well be a better/alternative way to achieve the same result, but this seems to work.
You need to remove existingResponse="Replace", I just had the same issue and that solved it for me. The default value is Auto.
Your answer lead me to find the real problem, so thanks! :)
Documentation
What would be the best (both human readable and fastest in runtime (say, compiled)) way to initialize a bean in Camel?
I need a bean to send it to JPA endpoint. The input is a Map created out of CSV. Looks like a trivial task, but I have just started with Camel, please help.
Currently I have managed to transform a Map into a more complicate Map that has the same strucure as my beans. I could write my own code that would traverse it, instantiate the lists and the lists members, employing Apache Beanutils, but I think there should be a faster way to initialize a bean right from the original Map, without creating an intermediate Map.
Here is what I currently have:
<route id="AgencyCsvInput" errorHandlerRef="agencyDeadLetters">
<from uri="direct:agencyCsvInput"/>
<split streaming="true">
<method ref="csvSplitter" method="tokenizeReader"/> <!-- prepends the first line of file for every subsequent line -->
<log message="splitted: ${body}" loggingLevel="DEBUG"/>
<setHeader headerName="inputRegistryLines">
<simple>${body}</simple>
</setHeader>
<unmarshal>
<csv delimiter=";" useMaps="true" />
</unmarshal>
<log message="unmarshalled: size: ${body.size()}, ${body}" loggingLevel="DEBUG"/>
<filter>
<simple>${body.size()} == 1</simple><!-- be sure to have spaces around an operator -->
<log message="filtered: listItem: ${body[0]['PATRONYMIC']}, list: ${body}" loggingLevel="DEBUG"/>
<setProperty propertyName="parser">
<simple>ref:patternParser</simple>
</setProperty>
<transform>
<spel><![CDATA[#{
{
dateOpen: properties['parser'].date(body[0]['ACTIVATION_DATE'], 'dd.MM.yyyy H:mm'),
balances: {
balance: {
{
typeCd: 'loanAmount',
amount: properties['parser'].comma(body[0]['GRANTED_CREDIT']),
currencyCd: body[0]['CURRENCY']
},
{
typeCd: 'fee',
amount: properties['parser'].comma(body[0]['Коммиссии']),
currencyCd: body[0]['CURRENCY'],
comment: 'Коммиссии'
},
{
typeCd: 'fine',
amount: properties['parser'].comma(body[0]['Пени']),
currencyCd: body[0]['CURRENCY'],
comment: 'Пени'
},
{
typeCd: 'penalty',
amount: properties['parser'].comma(body[0]['Штрафы']),
currencyCd: body[0]['CURRENCY'],
comment: 'Штрафы'
}
}
},
persons: {
{
typeCd: 'mainParty',
person: {
{
typeCd: 'individual',
names: {
name: {
{
typeCd: 'currentName',
lastName: body[0]['LAST_NAME'],
firstName: body[0]['FIRST_NAME'],
middleName: body[0]['PATRONYMIC']
}
}
},
birthDate: properties['parser'].date(body[0]['BIRTH_DATE'], 'dd.MM.yyyy H:mm'),
contacts: {
contact: {
{
typeCd: 'residentialAddress',
technologyCd: 'physical',
relationCd: 'home',
address: body[0]['COMMENTS']
},
{
typeCd: 'officeAddress',
technologyCd: 'physical',
relationCd: 'work',
address: body[0]['COMMENTS_WORK']
}
}
}
}
}
}
}
}
}]]></spel>
</transform>
<log message="transformed: ${body}" loggingLevel="DEBUG"/>
<to uri="direct:objectToDb"/>
</filter>
</split>
</route>
See how easy it is to transform a Map in a legible way, with just a couple of custom dates and number parser functions and SpEL inline Map/List instantiate syntax. It workedlike a charm when I had Hibernate "dynamic maps", so that my persistent objects were really the java.util.Maps. I'd wish to have the same easy way to instantiate a bean. It has some fields of List type, the type of listed objects is known, and they in turn may have other lists as their fields - all of them are based on beans with getters and setters.
Probably, the BeanShell has an inline bean initializing syntax? Or Camel has a way to invoke getters/setters and read the Map?
I want to avoid of scripting like this:
result = new myBean();
result.dateOpen = properties['parser'].date(body[0]['ACTIVATION_DATE'], 'dd.MM.yyyy H:mm');
balances = new Balances();
result.balances = balances;
balance = new List<Balance>();
reallyBalance = new Balance();
balance.add(balance);
reallyBalance.typeCd="loanAmount";
....
I find it ilegible.
It's a pure Camel question, but I'll mark it as Beanshell as well, hoping for hints of inline bean init syntax like:
bean = new MyBean(){dateOpen= properties['parser'].date(body[0]['ACTIVATION_DATE'], 'dd.MM.yyyy H:mm')....};
I'm trying to create a response to a REST web service call in a CXFRS Camel route, but no matter what I do the response to the client is always the same 200, not 201. Here's my route:
<route id="front-end">
<from uri="cxfrs:bean:myService" />
<setBody>
<constant>Will do...</constant>
</setBody>
<setHeader headerName="CamelHttpResponseCode">
<constant>201</constant>
</setHeader>
<setHeader headerName="Content-Type">
<constant>more/blah</constant>
</setHeader>
</route>
The body is returned but the response code and content type are ignored. What am I doing wrong?
Thanks,
Matt
Basically camel-cxfrs overwrites any headers set in exchange when it converts the exchange to actual HTTP response
See here:
exchange.getOut().setHeaders(binding.bindResponseHeadersToCamelHeaders(response, exchange));
And this happens because DefaultCxfRsBinding expects a jaxrs Response as a parameter.
So to fix the issue you either override DefaultCxfRsBinding with custom one in order to copy headers from exchange.getIn().
<cxf:rsServer id="MyService" address="/myAddress">
<cxf:binding><bean class="MyCustomCxfRsBinding" /></cxf:binding>
<cxf:serviceBeans>
<ref bean="myResourceWithJSR311Annotations" />
</cxf:serviceBeans>
</cxf:rsServer>
Or make your camel route to return a jaxrs Response with headers instead of setting headers in the rout or in camel processors. Something
class HttpHeaderProcessor implements Processor
{
#Override
public void process(Exchange exchange) throws Exception
{
Message message = exchange.getIn();
Response response = convertToJaxRs(message);
exchange.getIn().setBody(response);
exchange.getIn().setHeader("Test", "Won't work unless DefaultCxfRsBinding is not replaced with a custom one");
}
private Response convertToJaxRs(Message message)
{
ResponseBuilder jaxrsResponseBuilder = Response.ok(message.getBody(), MediaType.APPLICATION_XML);
jaxrsResponseBuilder.header("header1", "you'll see this");
Response response = jaxrsResponseBuilder.build();
return response;
}
}
For your sample:
<route id="front-end">
<from uri="cxfrs:bean:myService" />
<setBody>
set it to Response.ok(your message).header(x, y).build()
</setBody>
You can also use a Service bean returning a jaxrs.Response with headders
<route id="front-end">
<from uri="cxfrs:bean:myService" />
<bean ref="myServiceImpl">