I'm using cucumber and citrus together, and in my #Then definition, I have the citrus HTTP response:
#CitrusResource TestRunner runner;
runner.http(builder -> {
final HttpClientResponseActionBuilder rab =
builder.client("citrusEndpointAPI").receive()
.response(HttpStatus.OK).messageType(MessageType.JSON)
.contentType(MediaType.APPLICATION_JSON_VALUE);
Is there a way to store the returned JSON message body into a java JSON object?
You can use the local message store. Each message should be saved to that local in memory storage during the test. You can access the stored messages later in that test case via its name:
receive(action -> action.endpoint("sampleEndpoint")
.name("sampleMessage")
.payload("..."));
echo("citrus:message(sampleMessage.payload())");
Please note that we named the received message sampleMessage. You can access the message store via test context in custom test actions, too.
context.getMessageStore().getMessage("sampleMessage");
Besides that you could also use a custom message validation callback in Java DSL. Here you have full access to the received message content.
receive(action -> action.endpoint("sampleEndpoint")
.validationCallback((message, context) -> {
//Do something with message content
message.getPayload(String.class);
}));
Related
I'm using Java AWS SDK v1 to send messages to a SQS Queue, and I want to intercept this event to add some MessageAttributes in the message.
I created a new RequestHandler class that extend the RequestHandler2 from the SDK to modify it before the request:
class CustomMessagesAttributesRequestHandler : RequestHandler2() {
override fun beforeRequest(request: Request<*>) {
val originalRequest = request.originalRequestObject
if (originalRequest is SendMessageRequest) {
originalRequest.messageAttributes["my-custom-attribute"] =
MessageAttributeValue().withStringValue("123456789")
}
}
}
The solution above kinda works, it's called before the request, the Message Attribute is added in the originalRequestObject, but when the SQS client send the request it throws an exception:
Unable to calculate the MD5 hash of the message attributes
Digging into AWS SDK code, I see there is a default handler to check the message request with the result to compare both body and attributes MD5, and of course, since I modified the original object they does not match.
Is there a better way to achieve that besides the custom RequestHandler? Or there is a way to recalculate the originalRequestObject MD5?
I've declared a REST endpoint, which calls another route using direct. In the end of the second route I'm logging the body but it's not the same body returned to the browser.
Here's a small example reproducing the behavior (I'm using Apache Camel with Spring Boot):
#Component
public class EntidadeRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
restConfiguration().bindingMode(json);
rest("/entidade")
.get("/{id}")
.to("direct:search-by-id");
from("direct:search-by-id")
.routeId("search-by-id")
.setProperty("idEntidade", simple("${header.id}"))
.pollEnrich("file:files/mock/dados?noop=true&idempotent=false")
.unmarshal().json(JsonLibrary.Jackson)
.split(jsonpath("$"))
.filter(jsonpath("$[?(#.id == ${property.idEntidade})]"))
.marshal().json(JsonLibrary.Jackson)
.log("${body}");
}
}
I'm calling it from the browser, using the URL:
http://localhost:8080/camel/entidade/1 .
On the folder files/mock/dados I have a single file, called entidades.json where there's a JSON Array (see below).
I know the split and filter are working because I'm logging the body in that last line of code and this is what appears in the log:
2021-04-28 18:15:15.707 INFO 3905542 --- [nio-8080-exec-1] search-by-id : {"id":"1","tipo":"PF","nome":"João Maria"}
But this is what is returned to the browser (the exact content of the entidades.json file):
[{"id":"1","tipo":"PF","nome":"João Maria"},{"id":"2","tipo":"PF","nome":"Maria João"},{"id":"3","tipo":"PF","nome":"João Silva"},{"id":"4","tipo":"PF","nome":"José Souza"}]
Why the logged body is not the same that shows in the browser and to fix it?
PS: If I remove those marshal and unmarshal calls, I get the following error in the browser:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.apache.camel.component.file.FileBinding and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.apache.camel.component.file.GenericFile["binding"])
Without the input-file and knowing the concrete URI you called (including given value for path-variable {id}), I can only suspect some issues as follows:
Did you provide an id at the end of the GET-call ?
Why did you convert the JSON ?
Did you test the correct split ? Did you aggregate again ?
Did you want to log each message ?
REST endpoints
You specified the endpoint as GET /entidada/{id}.
The {id} is a path-variable.
So let's assume you call GET /entidata/1 with 1 as ID.
Then your JSON file is polled (read), unmarshalled to ... ?
JSON unmarshal/marshal
These unmarshal and marshal methods are either used to convert between different data-formats or from data-representation to Java-objects (POJOs) if they are internally used (e.g. to pass to a processor Bean etc.).
I suppose the file dados contains textual data as JSON.
So you can simply read this text into a (textual!) message body (like a text-message, compare JMS etc.) and work with it: (a) split by JSON-path, (b) filter by JSON-path, (c) log this JSON result, (d) send it back as HTTP-response to the calling client (e.g. your browser).
What happens on the split?
After this you try to split (assuming you have a JSON-array):
// incoming:
// a single enriched message, in body: JSON-array with 4 elements
.split(jsonpath("$")) // what do you expect as output ?
// here the split usually should be ended using `.end` DSL method
.filter(jsonpath("$[?(#.id == ${property.idEntidade})]")) // each array element of JSON body matched against id, e.g. 1
I filled your HTTP-response (JSON array with the 4 people) into online JSON-path evaluator. Evaluation of $ was not a split, but a single element (inside the result array): exactly the original array (with 4 people).
Why wasn't the file splitted into 4 messages, each containing a person-object?
Because your JSON-path $ denotes simply the root-element.
Plus: Usually after the .split() there follows an .end() which aggregates them again.
You left that out. I suppose that is an issue, too.
Filter works as expected
Later you filtered on the REST-given id:
.filter(jsonpath("$[?(#.id == ${property.idEntidade})]"))`
This results in the logged element:
{"id":"1","tipo":"PF","nome":"João Maria"}
The filter worked successfully: just leaving a single one with id 1.
Logging in Camel
Log EIP
When adding .log() to the route chain, this means you are using the Log EIP. In its documentation a warning Tip is given:
Logging message body with streamed messages:
If the message body is stream based, then logging the message body, may cause the message body to be empty afterwards. See this FAQ. For streamed messages you can use Stream caching to allow logging the message body and be able to read the message body afterwards again.
So your empty log message may be caused by a side-effect when using this for logging stream-based message bodies.
Difference of log methods
Explained in section Difference between log in the DSL and Log component:
The log DSL is much lighter and meant for logging human logs such as Starting to do … etc.
Below example (adjusted to your REST route) illustrates its usage:
rest("/entidade")
.get("/{id}")
.log("Processing ${id}")
.to("bean:foo");
Log component
I would suggest using the standard Log component by simply using .to() DSL passing a URI string based on schema log: together with the required parameter loggerName.
.to("log:filtered-json")
Here the URI-prefix for Log component is log:. Each message of the stream is logged using loggerName filtered-json.
The error was I need to pass an AggregationStrategy to the split. I also need to stop logging the body, because it was consuming the InputStream. After that, I could safely remove those marshal and unmarshal calls.
This is the final code:
#Component
public class EntidadeRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
restConfiguration().bindingMode(json);
rest("/entidade")
.get("/{id}")
.to("direct:search-by-id");
from("direct:search-by-id")
.routeId("search-by-id")
.setProperty("idEntidade", simple("${header.id}"))
.pollEnrich("file:files/mock/dados?noop=true&idempotent=false")
.split(jsonpath("$"), takeFirst(Exchange.FILTER_MATCHED))
.filter(jsonpath("$[?(#.id == ${property.idEntidade})]")).stop()
.end();
}
private AggregationStrategy takeFirst(String matchProperty) {
return ((oldExchange, newExchange) -> {
if (oldExchange == null && newExchange.getProperty(matchProperty, Boolean.class)) {
oldExchange = newExchange;
}
return oldExchange;
});
}
}
I have a requirement where I have to send message to Microsoft Teams.
I am trying to extract "to" channel name information from message I receive from queue and based on the channel name, I read it's url from properties file and send message. Below is the code for that.
RouteDefinition from = from("jms:queue:teamsq?connectionFactory=artemis");
from.setHeader("Exchange.CONTENT_TYPE", constant("application/json"));
final StringBuffer channelName = new StringBuffer();
from.process(exchange -> {
String[] dataArray = exchange.getIn().getBody(String.class).split(",", 2);
channelName.append(dataArray[0]);
exchange.getIn().setBody("{\"text\" : \"" + dataArray[1].trim() + "\"}");
})
.log("Body is : " + channelName + " : ${body}");
When body is logged, value of channelName is null.
Any help how can I get value of channelName outside this process() method?
Message received from queue is
channel1, This is test a message 5
Thanks in advance.
You can set a message header or an Exchange property. Both are kind of message variables to use during route processing.
.setHeader("channelName", channelName.toString())
.setProperty("channelName", channelName.toString())
The main difference is that Exchange properties are sitting on the Camel Exchange while message headers are part of the message itself.
The Camel Exchange is a Camel wrapper around the message. It is created when the message enters the route and thrown away at the end of the route.
Exchange Properties:
are only available during Camel route processing
are never sent to other systems
are only in-memory
Message headers:
are converted to message headers for the target system whenever the route does a routing to another system
are therefore sent to other systems
are serialized when sent to another system
If you send a message from a Camel route to a JMS queue and consume it from another route, the Exchange properties are no more available while the message headers are still present.
However, if you route to a direct endpoint (Camel in-memory endpoint), the whole Exchange is transferred and Exchange properties are still available.
I'm currently trying to test a small application that returns the sum of two numbers.
For testing I use Citrus. Is it possible to read the content of the respons?
The code below sends a request to the server and the server responds well. Everything works fine, but I want to know exactly what the server returns.
runner.http(action -> action.client(httpClient)
.send()
.get("?value1=1&value2=2"));
runner.http(httpActionBuilder -> httpActionBuilder
.client(httpClient)
.receive()
.response(HttpStatus.OK));
Besides giving the expected Http response code in the receive action you can also provide the expected message body as payload in the receive action. Just use the payload() methods on receive action builder right after response().
Citrus will automatically verify the response body content (Json, XML, plaintext) with the expected payload. If there are unexpected differences the test will fail.
Hope that helps.
how can I access a custom header from a server response when using TransferManager ?
we have a custom header added in the response from our server, from the client side we use multi part upload with default transfer manager
any suggestion how in how i could hook up it ?
so basically i want to pass over the response from the return response.getAwsResponse(); found in the class: AmazonS3Client on the method
private <X, Y extends AmazonWebServiceRequest> X invoke(Request<Y> request,
HttpResponseHandler<AmazonWebServiceResponse<X>> responseHandler,
String bucket, String key, boolean isAdditionalHeadRequestToFindRegion) {
that response will have the HTTP response from the server containing the custom heather which I'm after, basically is a unique Id send back when the file was 100% completed so than i can manipulate it.
I need to pass over this custom header from the response to the very beginning where I use the transfer manager and the upload.waitForCompletion(),
also i don't want to edit the amazon's,
so does anyone know if there is an interface or some other object which provides me access to it ?
After some debug into the framework I strongly believe that there is no way to have access to the http response when using the TransferManager
for what we are trying to do we need to send an unique id from the server to the client when the file upload is completed and assembled
** therefore if you don't mind in do not use the beauty of the TransferManager you could write "your own TransferMananger" than you will have full control, but again on the client side we don't really want to add custom code but have a standard and simple approach (but that is for my scenario), if you decide to do it manually it can be done I have already tried and works !
So as a alternative we though in send from the server via the eTag, which is not great but will do the job and will keep the client side simple and clean
Any suggestion in how to send this value back in a better way ?
Upload up = tm.upload(bucketName, file.getName(), file);
UploadResult result = (UploadResult) ((UploadImpl) up).getMonitor().getFuture().get();
String uniqueIdFromServer = result.getETag();