Camel Inter module coordination - dependency - apache-camel

My problem is I have a routeBuilder which does routing based on url patterns to different modules say A, B, D as follows (the customProcessor adds the ARestURN, BRestURN etc to the header which are comma seperated url patterns that will be matched against the in header CamelHttpUri to route to correct module)
from("servlet:///?matchOnUriPrefix=true")
.process(customProcessor)
.choice()
.when(simple("${in.headers.ARestURN} contains ${in.headers.CamelHttpUri}"))
.to("http4://" + AUrl + "?bridgeEndpoint=true&throwExceptionOnFailure=false")
.when(simple("${in.headers.BRestURN} contains ${in.headers.CamelHttpUri}"))
.to("http4://" + BUrl + "?bridgeEndpoint=true&throwExceptionOnFailure=false")
.otherwise()
.to("http4://" + DUrl + "?bridgeEndpoint=true&throwExceptionOnFailure=false");
My question is now I need to service a REST url request which requires me to acccess AUrl and BUrl and aggregate the results and then respond to the request how do I achieve this ?
I thought of writing something as below
from("servlet:///?matchOnUriPrefix=true")
.process(customProcessor)
.choice()
.when(simple("${in.headers.ARestURN} contains ${in.headers.CamelHttpUri}"))
.to("http4://" + AUrl + "?bridgeEndpoint=true&throwExceptionOnFailure=false")
.when(simple("${in.headers.BRestURN} contains ${in.headers.CamelHttpUri}"))
.to("http4://" + BUrl + "?bridgeEndpoint=true&throwExceptionOnFailure=false")
.when(simple("${in.headers.MultiModuleRestURN} contains ${in.headers.CamelHttpUri}"))
.to("direct:multimodule")
.otherwise()
.to("http4://" + DUrl + "?bridgeEndpoint=true&throwExceptionOnFailure=false");
from("direct:multimodule")
.process(new MyProcessor())
.to("http4://" + AUrl + "?bridgeEndpoint=true&throwExceptionOnFailure=false");
But I don't get howto get the results from first "AUrl" (which is a REST service returning json results) and do some process on it and provide specific values of the result to the next url which is BUrl to get results from it and process that and combine results and send it back to the calling service.
eg:
the REST call : /AB/123/getPrice
Need to pass 123 to Module A - REST call -> /A/123/getId response-{A: 123, id: x1}
Need to pass x1 to Module B - REST call -> /B/x1/getPrice response-{id: x1, p:$10}
Need to return to caller {name: 123, id: x1, price: $10 } (json combined object)
Update: Did as follows for now,
Note: removed bridgeEndpoint stuff for clarity
from("servlet:///?matchOnUriPrefix=true")
.process(customProcessor)
.choice()
.when(simple("${in.headers.multiRestURN} contains ${in.headers.CamelHttpUri}"))
.to("direct:multimodule")
.when(simple("${in.headers.ARestURN} contains ${in.headers.CamelHttpUri}"))
.to("http4://" +AUrl)
.when(simple("${in.headers.BRestURN} contains ${in.headers.CamelHttpUri}"))
.to("http4://" +BUrl)
.otherwise()
.to("http4://" +DUrl);
//
from("direct:multimodule")
.to(ExchangePattern.InOut,"http4://"+AUrl)
.convertBodyTo(String.class)
.process(customAProcess)
.to(ExchangePattern.InOut, "http4://"+BUrl+"/resources")
.convertBodyTo(String.class)
.process(customBProcessor);
In customAProcess I do a
exchange.getIn().setHeader("AData", exchange.getIn().getBody(String.class));
So this data is available when BUrl request comes with result, I am sending to BUrl unnecessary data but this solves the issue hopefully, maybe when I get time I will look into enricher and aggregate strategy to solve the issue.

Enricher component might be what you are looking for. It's implementation of one of common patters. Please, note that it is possible to pass implementation of aggregation strategy via strategyRef parameter.
While implementing aggregation strategy for your first rest call you are able to enrich original exchange by returned data of your first rest response - see interface of AggregationStrategy, please. Just create beans of your strategies and configure enricher.
Hope it helps. Example context :) :
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="servlet:///?matchOnUriPrefix=true"/>
<enrich uri="http4://serviceA" strategyRef="strategyA"/>
<enrich uri="http4://serviceB" strategyRef="strategyB"/>
</route>
</camelContext>
<bean id="strategyA" class="..." />
<bean id="strategyB" class="..." />
You can do whatever you want with your exchanges, both original (being enriched) and new (enriching) in aggregation strategies.

Related

Camel cxfws producer, POJO format, throws "Get the wrong parameter size to invoke the out service" error

I have a similar situation as Camel CXF POJO mode using Java DSL where
I have a wsdl https://api.stage.eventcore.com/ReportService.asmx?WSDL
Created a wsdl2java library
Using camel cxf producer component, with POJO format to make a SOAP request.
Setting the operationname, operationnamespace as headers. cxfEndpoint is also configured accurately.
Below is the error I am getting for "GetReport" operation.
java.lang.IllegalArgumentException: Get the wrong parameter size to invoke the out service, Expect size 7, Parameter size 4.
Please check if the message body matches the CXFEndpoint POJO Dataformat request.
Here is the binding info for the operation I am dealing with.
<wsdl:operation name="GetReport">
<soap:operation soapAction="https://api.eventcore.com/GetReport" style="document"/>
<wsdl:input>
<soap:body use="literal"/>
<soap:header message="tns:GetReportAuthTokenHeader" part="AuthTokenHeader" use="literal"/>
<soap:header message="tns:GetReportCredentialsHeader" part="CredentialsHeader" use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
<soap:header message="tns:GetReportAuthTokenHeader" part="AuthTokenHeader" use="literal"/>
</wsdl:output>
</wsdl:operation>
As part of the exchange body I am sending a list of objects with CredentialHeader, GetReport Object with necessary data. I also added holder parameters for Response message.
GetReport getReport = new GetReport();
getReport.setReportID(123);
getReport.setSortColumn("LastModified");
getReport.setStartRow(1);
getReport.setEndRow(2);
getReport.setSortAscending(true);
ReportFilter filter = new ReportFilter();
filter.setField("LastModified");
filter.setComparison(ComparisonType.GREATER_THAN);
filter.setMatchValue("2018-05-09T23:23:51.8769404Z");
filter.setMode(FilterMode.SELF);
getReport.setFilter(filter);
CredentialsHeader credentials = new CredentialsHeader();
credentials.setUserName("foo");
credentials.setPassword("bar");
credentials.setEventID(11111);
List<Object> params = new ArrayList<Object>();
params.add(getReport);
params.add(credentials);
params.add(null); //params.add(new AuthTokenHeader());
params.add(new javax.xml.ws.Holder<AuthTokenHeader>());
//params.add(new javax.xml.ws.Holder<GetReportResponse>());
exchange.getIn().setBody(params);
here is the cxfEndpoint configuration.
org.apache.camel.component.cxf.CxfEndpoint endpoint_cSOAP_1 = getCxfEndpoint(
"cxf://"
+ "https://api.stage.eventcore.com/ReportService.asmx"
+ "?dataFormat=POJO"
+ "&serviceClass="
+ "tableau.ea.eventcore.api.reportservice.ReportServiceSoap"
+ "&serviceName="
+ "{https://api.eventcore.com/}ReportService"
+ "&endpointName="
+ "{https://api.eventcore.com/}ReportServiceSoap"
+ "&defaultOperationNamespace="
+ javax.xml.namespace.QName.valueOf(
"{https://api.eventcore.com/}GetReport")
.getNamespaceURI()
+ "&defaultOperationName="
+ javax.xml.namespace.QName.valueOf(
"{https://api.eventcore.com/}GetReport")
.getLocalPart() + "&" + "loggingFeatureEnabled"
+ "=" + "true" + "&" + "continuationTimeout" + "="
+ 600000
+ "&headerFilterStrategy=#CXF_PAYLOAD_HEADER_FILTER"
+ "&properties.id=" + "cSOAP_1", false, false, false,
(String[]) null);
What I don't understand is , why cxf expecting 7 parameters? What are those 7 params? I tried making it 7 by adding some nulls, but it fails with "argument type mismatch" error. Please help me understand Input message parts in this particular operation.
I did workaround this issue by NOT using "-exsh true" option while generating wsdl, and setting the SOAP headers using Holders.LIST header. While doing that I had to set mustUnderstand = true for CredentialsHeader.
finally I got the headers added to the SOAP request. But, I really want to know why the path of passing all headers plus body as list of parameters ( with "-exsh true" option) didn't work. I had exact same approach working for a different webservice, but not for this one. I am curious what is making the difference.
Pls share if anyone has insight.

Camel Routing Config File with Query Params

I have a '.route' file for a rest service that works, it has the resource in the middle of the URI:
<from uri='restlet:/foo/{id}/bar
This works just fine, I am able to retrieve the 'id' in code using:
String id = e.getIn().getHeader("id", String.class);
Now, I want a '.route' with a URI with a query parameter in it.
I tried a bunch of ways, like:
<from uri='restlet:/foo/baz?color={aColor}
But this does not work, I get a 404 error, the server cannot find the URI.
This seems to be a very easy/general thing, anybody know how to do this?
I looked over the docs, but I cannot figure out how to do it.
All params follow after the question mark receive as the camel options(for example restletMethod, connectionTimeout.... see http://camel.apache.org/restlet.html). Just use <from uri='restlet:/foo/{id}/bar in your route, pass the parameters in query like a http://localhost:8080/mywebapp/rs/foo/1234/bar?color=red and get it String id = e.getIn().getHeader("color", String.class);

Camel pollEnrich and xml 'prettyPrint'

I am attempting to use Camel's pollEnrich feature, but it is not behaving as I would like... I'm not saying it's broken, but wondering if there is a way to get the behavior I desire. That is, I have an XML (blueprint) defined route that goes something like this:
<route>
<from uri="direct:a" />
<pollEnrich uri="http:www.somewebsite.com?format=application/xml" />
<to uri="log:com.acme?level=WARN&showStreams=true" />
</route>
Now, the response normally comes back just fine (e.g., in a web browser). The problem seems to be that it is not just on one line, and for some reason Camel reads each line, sequentially into the same buffer, starting at character zero... so what we end up with is one messy line in the output from the pollEnrich. That is, the to uri="log... line prints messages like:
2015-05-26 13:55:26,379 | WARN | a.distr.topic.B] | contentEnrich |
? ? | 142 - org.apache.camel.camel-core - 2.12.0.redhat-610379 |
Exchange[ExchangePattern: InOnly, BodyType:
org.apache.camel.converter.stream.InputStreamCache, Body:
<?xml versi</ElementStatus> ]pe></Status>nd>gin>ys for this element.</Reason>>ame>
(last line vertically offset for emphasis)
I cannot seem to find a way to tell Camel that the result will be in 'prettPrint' format... Anyone know how? The documentation seems to suggest that this option does not exist--in which case, I'd consider this to be a bug... though I suppose a person could argue that a custom aggregation strategy should be used (and I'd disagree with that person, citing the simplicity of this case) :)
UPDATE#1: even using org.apache.camel.processor.aggregate.UseLatestAggregationStrategy produces the same effect. (i.e., usage as below)
<bean id="latestStrat"
class="org.apache.camel.processor.aggregate.UseLatestAggregationStrategy" />
<route>
<from uri="direct:a" />
<pollEnrich uri="http:www.somewebsite.com?format=application/xml" strategyRef="latestStrat" />
<to uri="log:com.acme?level=WARN&showStreams=true" />
</route>
...going to cross fingers and try org.apache.camel.processor.aggregate.GroupedExchangeAggregationStrategy, but am guessing there is a configuration limitation with Camel always treating EOL characters as message delimiters.
UPDATE #2 - additional information:
The REST(GET) response received (tested with wget) has blank lines and null fields--but no carriage returns (^M). I've tried both the http and http4 components--same result. There is a leading <?xml version="1.0" encoding="UTF-8"?>, but no namespace/style info. I also just noticed that tab characters have been used for the pretty-ish indents. In sum, the response looks like:
<?xml version="1.0" encoding="UTF-8"?><ElementStatus>
<Flag>false</Flag>
<CODE>XYZ</CODE>
<Locale>Western</Locale>
...
(again, where the whitespace indenting has been done with tabs--AND the blank lines have a few tabs too)
...so the "answer" is that this is an apparent limitation of (or bug within) the log component's "showStreams" logic. I implemented Processor in a <bean>, routed the Exchange output from my pollEnrich to that <bean>, and logged the contents instead, and that matches exactly the output from wget.
FYI: this is camel-paxlogging (2.12.0.redhat-610379) - not sure what underlying version of camel that corresponds to, as I don't seem to have a jboss-parent-2.12.0 pom in my maven repo--which is strange, since I have other jboss-parent poms--and the red hat documentation doesn't seem to get into version composition.
FYI#2: and on a related note, when I use GroupedExchangeAggregationStrategy it does produce a List<Exchange>, BUT it behaves effectively the same as UseLatestAggregationStrategy -- i.e., 'grouped' produces a one-item List<Exchange> that only has the pollEnrich result, where 'latest' produces a standalone Exchange object that has only the pollEnrich result. Seems like an error in either GroupedExchangeAggregationStrategy or pollEnrich ... but this will likely be the topic of my next Stack-post.

Accessing camel properties in DSL

I have properties defined in camel context xml as follows. These properties will vary for different calls coming from API's exposed.
<util:properties id="abc-properties">
<prop key="serviceId"></prop>
<prop key="name"></prop>
</util:properties>
<propertyPlaceholder location="ref:abc-properties" id="properties" />
In my Java DSL I am trying to access these properties as follows in 'from' and in 'to'
from(Identifier + ":" + abcName + "://{{serviceId}}")
to(Identifier + ":" + abcName + "://{{serviceId}}")
When I my DSL routes are formed, I see that for 'to' I don't get updated value of serviceId. Value which had come first time continues to appear for all subsequent calls. for 'from' I always get updated value for serviceId.
By referring post Use Exchange Property in Camel DSL "to" , I tried using recipientList but that too dosen't seems to be working.
Do I need to use any other syntax to access property in 'to' ?
Please try this:
from(
Identifier + ":" + abcName + "://{{serviceId}}"
).recipientList(
simple(Identifier + ":" + abcName + "://${properties:serviceId}")
)

Camel PollEnrich not working properly

After transforming a file with xslt I have to append a file downloaded from ftp. So I did the following:
from("direct:adobe_productList_incremental")
.id("routeADOBESPtransformPI_productList")
.log(LoggingLevel.INFO, "---------Starting file: ${body}")
.convertBodyTo(InputStream.class)
.to("xslt:classpath:" + xsltTransformationProductList)
.log(LoggingLevel.INFO, "---------Transformed file: ${body}")
.pollEnrich(ftpType+"://"+ftpUsername+"#"+ ftpUrl +":" + ftpPort + ftpPath_incrementalComplete +"?password="+ftpPassword+"&fileName="+ftpFilename_incrementalComplete+"&passiveMode=true&binary=true&delete=false",10000)
.log(LoggingLevel.INFO, "---------After poll enrich: ${body}")
.to("file:{{file.root}}{{file.outbox.products_list_incremental}}?fileName={{file.outbox.products_list_incremental.name}}.final");
untill the poll everythin works (the transformation is done correctly), but after the pollEnrich the current body is overrided by the ftp content (and not appended as it should be).
Any help?
No it works as designed.
By default the content will be overridden. If you need to append/merge or whatever, you need to use a custom aggregation strategy, and implement code logic that does this.
See the Camel docs at: http://camel.apache.org/content-enricher.html about the ExampleAggregationStrategy.
The Camel docs says
The aggregation strategy is optional. If you do not provide it
Camel will by default just use the body obtained from the resource.

Resources