Parse Correct encoding with Kotlin rest template - spring

I want to get the labels from the wiki api.
And I am using rest template in Kotlin with Spring.
I also tried OKHttp in Java but get the same results.
It worked in Postman.
val uri = "https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q1726&format=json&props=labels&languages=de|zh"
val restTemplate = RestTemplate()
val result = restTemplate.getForObject(uri, ByteArray::class.java)
if (result != null)
print(String(result, charset("UTF-8")))
Output is
{"entities":{"Q1726":{"type":"item","id":"Q1726","labels":{"zh":{"language":"zh","value":"\u6155\u5c3c\u9ed1"},"de":{"language":"de","value":"M\u00fcnchen"}}}},"success":1}[B#6a71619f
I expect it to be "München" and "慕尼黑"
How to I parse the encoded strings correctly?

This answer did the trick.
Using StringEscapeUtils from org.apache.commons.text because the other is deprecated.

Related

Spring reactive WebClient GET json response with Content-Type "text/plain;charset=UTF-8"

I’m having an issue with Spring 5 reactive WebClient, when I request an endpoint that returns a correctly formated json response with content type "text/plain;charset=UTF-8".
The exception is
org.springframework.web.reactive.function.UnsupportedMediaTypeException:
Content type 'text/plain;charset=UTF-8' not supported for bodyType=MyDTOClass
Here is how I made the request:
webClient.get().uri(endpoint).retrieve().bodyToFlux(MyDTOClass.class)
EDIT: Headers are "correctly" setted (Accept, Content-Type), I have tried differents content-types (json, json + UTF8, text plain, text plain + UTF8) conbinations, without success. I think the issue is .bodyToFlux(MyDTOClass.class) doesn't know how to translate "text" into MyDTOClass objects.
If I change the request to:
webClient.get().uri(endpoint).retrieve().bodyToFlux(String.class)
I can read the String.
EDIT 2: The next quote is extracted from the Spring documentation
(https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-codecs-jackson)
By default both Jackson2Encoder and Jackson2Decoder do not support
elements of type String. Instead the default assumption is that a
string or a sequence of strings represent serialized JSON content, to
be rendered by the CharSequenceEncoder. If what you need is to render
a JSON array from Flux<String>, use Flux#collectToList() and encode a
Mono<List<String>>.
I think the solution is define a new Decoder/Reader in order to transform the String into MyDTOClass, but i don't know how to do it.
In case someone needs it, here is the solution:
This answer (https://stackoverflow.com/a/57046640/13333357) is the key. We have to add a custom decoder in order to specify what and how deserialize the response.
But we have to keep in mind this: The class level anotation #JsonIgnoreProperties is setted by default to the json mapper and does not have effect to other mappers. So if your DTO doesn't match all the response "json" properties, the deserialization will fail.
Here is how to configure the ObjectMapper and the WebClient to deserialize json objects from text responses:
...
WebClient.builder()
.baseUrl(url)
.exchangeStrategies(ExchangeStrategies.builder().codecs(configurer ->{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
configurer.customCodecs().decoder(new Jackson2JsonDecoder(mapper, MimeTypeUtils.parseMimeType(MediaType.TEXT_PLAIN_VALUE)));
}).build())
.build();
...
Cheers!
Following up on this answer from Charlie above, you can now add an extra "codec" without replacing them.
You can also easily build Spring's default configured ObjectMapper via Jackson2ObjectMapperBuilder.json().build()
Here's an example that reuses the ObjectMapper from the built-in Jackson2JsonDecoder
var webClient = webClientBuilder
.baseUrl(properties.getBaseUrl())
.codecs(configurer -> {
// This API returns JSON with content type text/plain, so need to register a custom
// decoder to deserialize this response via Jackson
// Get existing decoder's ObjectMapper if available, or create new one
ObjectMapper objectMapper = configurer.getReaders().stream()
.filter(reader -> reader instanceof Jackson2JsonDecoder)
.map(reader -> (Jackson2JsonDecoder) reader)
.map(reader -> reader.getObjectMapper())
.findFirst()
.orElseGet(() -> Jackson2ObjectMapperBuilder.json().build());
Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(objectMapper, MediaType.TEXT_PLAIN);
configurer.customCodecs().registerWithDefaultConfig(decoder);
})
.build();
Set content type for webclient.
webClient.get()
.uri(endpoint)
.contentType(MediaType.APPLICATION_JSON_UTF8)

Spring RestTemplate API query parameter encoding for doing a GET HTTP Request

The url-string contains a back-slash character that needs to be encoded. The url string is as follows.
String folder = "\\Foo\\Bar\\"; // some folder search path.
String urlString= "http://localhost:8081/certificates/?mypath=%5CFoo%5CBar%5C" // (after encoding)
Here I use Spring RestTemplate to do a GET request. I setup a mock-server to examine the request in detail (mock server setup using Mulesoft, if u must know!).
ResponseEntity<String> responseEntity = api.exchange(urlString, HttpMethod.GET, new HttpEntity<>(new HttpHeaders()), String.class);
Here I use plain vanilla Java URLConnection to perform the request. Attached image with detailed request snapshot.
// 2. Plain vanilla java URLConnection. "result.toString()" has certificate match.
StringBuilder result = new StringBuilder();
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("X-Venafi-Api-Key", apiKey);
conn.setRequestMethod("GET");
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
result.append(line);
}
rd.close();
System.out.println(result.toString());
In the images, you can see that the queryString value is different for these two requests. One of them shows \\ while the other shows %5C, although the parsed parameter value for myPath is still the same.
I am having to deal with an api that seems to work if-and-only-if the queryString looks like the former (i.e. "\\"). Why does the parsed queryString for Spring show "%5C" while this value shows double-backslash for requests originating from plain Java, curl, and even a simple browser?
What baffles me EVEN more, is that just about everything about the two HTTP Requests are IDENTICAL! And yet, why does the queryString/requestUri parse differently for these two requests? Shouldn't it be that a HTTP GET method is completely defined by its header contents and the requestUri? What am I missing to capture in these two GET requests?
Lots of questions. Spent an entire day, but at least I could verify that the way the requestUri/queryString is parsed seems to align with how the remote api-server responds.
Thanks.
Did some digging around the following morning. Turn out, with
ResponseEntity<String> responseEntity = api.exchange(urlString, HttpMethod.GET, new HttpEntity<>(new HttpHeaders()), String.class);
You should NOT have the "urlString" already encoded. The 'exchange' method does that encoding for you under-the-hood.

webserver using spring boot kotlin in Corda with XML

I am developing corda application using kotlin. I am on webserver spring boot. My requirment is to recieve(postman or any API from outside) XML in requestBody and create IOU(pass) that xml as it is to Flow but i am not able to so. when i define it as a string and pass that xml i am able to do it. but for xml i am struggling. Can anyone help please. Below is my code. am doing anything wrong here. The problem is- i dont get error but it just doesnt work.
#PostMapping(value = ["createTransaction"],consumes = [MediaType.APPLICATION_XML_VALUE],produces = [ MediaType.TEXT_PLAIN_VALUE])
private fun TransactionOne(#RequestBody ()employee:Document, #RequestParam(value = "payload") payload: String, #RequestParam(value = "partyName") partyName: String): ResponseEntity<String> {
val partyX500Name = CordaX500Name.parse(partyName)
val otherParty = proxy.wellKnownPartyFromX500Name(partyX500Name) ?: return ResponseEntity.badRequest().body("Party named $partyName cannot be found.\n")
return try {
val signedTx = proxy.startTrackedFlow(::IOUFlow, employee, otherParty).returnValue.getOrThrow()
ResponseEntity.status(HttpStatus.CREATED).body("Transaction id {$signedTx} committed to ledger.\n")
} catch (ex: Throwable) {
logger.error(ex.message, ex)
ResponseEntity.badRequest().body(ex.message!!)
}
}
This is not clear what does it mean "just doesn't work". You don't have response from "startTrackedFlow"?
What do you see in logs of your nodes? I think the answer is there.
There is not enough information to help you...

How to make Feign POST request without a request body and with query params?

I am using Feign with the Apache Http Client and I would like to support the following jax-rs interface:
#POST
#Path("/do_something")
void doSomething(#QueryParam("arg") String arg);
But, ApacheHttpClient uses a RequestBuilder, which converts query parameters for requests without a body/entity into a UrlEncodedFormEntity.
I am converting my APIs to jax-rs, and I do not want to break backwards compatibility. Is there a way to use Feign without adjusting my API? Will the OkHttp or Ribbon clients support POSTs with query params and no body/entity? Is there another java jax-rs client that will support this?
Also, is there a reason why RequestBuilder turns query params into a UrlEncodedFormEntity? Is there an alternative HttpUriRequest builder within the apache-httpclient library that doesn't do this? RequestBuilder's build method has the following lines of code:
if (entity == null && (HttpPost.METHOD_NAME.equalsIgnoreCase(method) || HttpPut.METHOD_NAME.equalsIgnoreCase(method))) {
entity = new UrlEncodedFormEntity(parameters, HTTP.DEF_CONTENT_CHARSET);
} else {
// omitted expected behavior
}
Before switching to Feign, my code constructed a HttpUriRequest with something similar to the following:
URI uri = new URIBuilder()
.setScheme("https")
.setHost("localhost")
.setPath("service/do_something")
.addParameter("arg", "value")
.build();
HttpUriRequest request = new HttpPost(uri);
If you are willing to break the API slightly and maintain support for the #QueryParam, then you could define a request interceptor on the feign client that adds a plain text entity/body to the request:
.requestInterceptor(template -> {
if (template.method().equals(HttpPost.METHOD_NAME) && template.queries().keySet().size() > 0 && template.body() == null) {
template.body(" ");
}
})
Then, your API would change with the following:
#POST
#Consumes(MediaType.TEXT_PLAIN)
#Path("/do_something")
void doSomething(#QueryParam("arg") String arg);
But, this breaks the API since the server now expects/consumes a POST message with a plain text entity/body.
I think the same could be accomplished without the requestInterceptor and with Feign's #Body template:
#POST
#Consumes(MediaType.TEXT_PLAIN)
#Body(" ")
#Path("/do_something")
void doSomething(#QueryParam("arg") String arg);
But, this means that your API would have to include Feign rather than pure jax-rs annotations.

How to query SOAP based endpoint using Spring WS by passing pre generated SOAP Request instead of XML payload?

Need to query SOAP based endpoint using Sprint WS - but here instead of passing XML payload I will have pass a generated SOAP Request itself. I was using org.springframework.ws.client.core.WebServiceTemplate for this purpose right now ?
You can do the following:
ClientMessageCallBack _callBack =
new ClientMessageCallBack("yournamespaceuri/operationx");
final String _message =
"<ns1:operationx xmlns:ns1="yournamespaceuri"></ns1:operationx>";
StreamSource _source = new StreamSource(new StringReader(_message));
// the result will be output in the console
StreamResult _result = new StreamResult(System.out);
// suppose you have a reference to webServiceTemplate
webServiceTemplate.sendSourceAndReceiveToResult(_source,_callBack, _result);

Resources