WebFlux parsing request body in WebFilter - spring-webflux

What is the right way to take the value from requestBody and put it in the Header?
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return exchange.getRequest()
.getBody()
.map(dataBuffer -> dataBuffer.asInputStream(true))
.reduce(SequenceInputStream::new)
.map(inputStream -> new BufferedReader(new InputStreamReader(inputStream)).lines()
.collect(Collectors.joining("\n")))
.map(this::getRequestRqUid)
.flatMap(rqUid -> chain.filter(exchange)
.subscriberContext(ctx -> ctx.put(RQUID_HEADER, rqUid)));
}
The problem is that I open Flux DataBuffer
How to properly close it or use a copy with DefaultDataBufferFactory?

It turned out to implement as a basis took ServerWebExchangeUtils from spring-cloud-gateway-server
#Order
#Slf4j
#Component
#RequiredArgsConstructor
public class PrepareHeaderWebFilter implements WebFilter {
private static final byte[] EMPTY_BYTES = {};
public static final String CACHED_REQUEST_BODY_ATTR = "cachedRequestBody";
public static final String CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR = "cachedServerHttpRequestDecorator";
private static final List<HttpMessageReader<?>> MESSAGE_READERS = HandlerStrategies.withDefaults()
.messageReaders();
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return cacheRequestBody(exchange, serverHttpRequest -> {
var exchangeMutate = exchange.mutate()
.request(serverHttpRequest)
.build();
var serverRequest = ServerRequest.create(exchangeMutate, MESSAGE_READERS);
return serverRequest.bodyToMono(BaseRequest.class)
.map(baseRequest -> {
exchange.getAttributes()
.put(BaseRequest.Fields.rqUID, baseRequest.getRqUID());
return baseRequest;
})
.then(removeCacheAndChain(exchange, chain));
});
}
private static Mono<Void> cacheRequestBody(ServerWebExchange exchange, Function<ServerHttpRequest, Mono<Void>> function) {
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory factory = response.bufferFactory();
return DataBufferUtils.join(exchange.getRequest()
.getBody())
.defaultIfEmpty(factory.wrap(EMPTY_BYTES))
.map(dataBuffer -> decorate(exchange, dataBuffer))
.switchIfEmpty(Mono.just(exchange.getRequest()))
.flatMap(function);
}
private static Mono<Void> removeCacheAndChain(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest cachedRequest = exchange.getAttribute(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR);
Assert.notNull(cachedRequest, "cache request shouldn't be null");
exchange.getAttributes()
.remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR);
return chain.filter(exchange.mutate()
.request(cachedRequest)
.build());
}
private static ServerHttpRequest decorate(ServerWebExchange exchange, DataBuffer dataBuffer) {
if (dataBuffer.readableByteCount() > 0) {
if (log.isTraceEnabled()) {
log.trace("retaining body in exchange attribute");
}
exchange.getAttributes()
.put(CACHED_REQUEST_BODY_ATTR, dataBuffer);
}
ServerHttpRequest decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
#Override
public Flux<DataBuffer> getBody() {
return Mono.fromSupplier(() -> {
if (exchange.getAttributeOrDefault(CACHED_REQUEST_BODY_ATTR, null) == null) {
// probably == downstream closed or no body
return null;
}
if (dataBuffer instanceof NettyDataBuffer) {
NettyDataBuffer pdb = (NettyDataBuffer) dataBuffer;
return pdb.factory()
.wrap(pdb.getNativeBuffer()
.retainedSlice());
} else if (dataBuffer instanceof DefaultDataBuffer) {
DefaultDataBuffer ddf = (DefaultDataBuffer) dataBuffer;
return ddf.factory()
.wrap(Unpooled.wrappedBuffer(ddf.getNativeBuffer())
.nioBuffer());
} else {
throw new IllegalArgumentException("Unable to handle DataBuffer of type " + dataBuffer.getClass());
}
})
.flux();
}
};
exchange.getAttributes()
.put(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR, decorator);
return decorator;
}
}

Related

How do i get data from Rest API

Please, i am trying to access data from rest API,
From the Documentation ,to get the token, i have to make a post call ,then use the token received to access two other results:
for the token, they gave this from the documentation:
https://apps.qa.interswitchng.com/passport/oauth/token
with the following Query Params
grant_type string
Headers
Authorization string required
Set value to "Basic Base64(CLIENT_ID:SECRET_KEY)"
Content-Type application/x-www-form-urlencoded
string
My Codes
#SuppressLint("StaticFieldLeak")
class SendTokenReqAsyncTask extends AsyncTask<String, Void, String> {
#Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setMessage("processing Token");
progressDialog.setCancelable(false);
progressDialog.show();
}
#Override
protected String doInBackground(String... params) {
JSONObject jsonObject2 = new JSONObject();
InputStream inputStream = null;
String result2 = "";
base64EncodedCredentials = "Basic" + Base64.encodeToString ((CLIENT_ID + ":" + SECRET) .getBytes (), Base64.NO_WRAP);
try {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(" https://apps.qa.interswitchng.com/passport/oauth/token/");
//StringEntity stringEntity2 = new StringEntity(userPhoneNumber);
//httpPost.setEntity(stringEntity2);
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Authorization", base64EncodedCredentials);
httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
HttpResponse httpResponse1 = httpclient.execute(httpPost);
inputStream = httpResponse1.getEntity().getContent();
InputStreamReader inputStreamReader1 = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader1);
StringBuilder stringBuilder2 = new StringBuilder();
String bufferedStrChunk = null;
int data = inputStreamReader1.read();
while((bufferedStrChunk = bufferedReader.readLine()) != null){
stringBuilder2.append(bufferedStrChunk);
}
result2=stringBuilder2.toString();
return result2;
} catch (Exception e) {
Log.d("InputStream", e.getLocalizedMessage());
}
return null;
}
#Override
protected void onPostExecute(String result2) {
super.onPostExecute(result2);
progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setCancelable(true);
progressDialog.hide();
//String re=null;
/*try {
JSONObject jsonObject = new JSONObject(result2);
re = jsonObject.getString(result2);
} catch (JSONException e) {
e.printStackTrace();
}*/
txtInfo = findViewById(R.id.textViewMain);
txtInfo.setVisibility(View.VISIBLE);
txtInfo.setText(MessageFormat.format("Token is :{0}", result2));
}
}
with the below code in the oncreate
SendTokenReqAsyncTask myAsyncTasks = new SendTokenReqAsyncTask();
myAsyncTasks.execute();
i also used retrofit this way
#SuppressWarnings("deprecation")
public class MyRetrofitClient {
protected static MyRetrofitClient myRetrofitClient = null;
private static RetroApis myRetroApis;
private static TokenAPI tokenAPI;
private static Retrofit retrofit = null;
private String tokenFromServer;
String clientID,secret;
private byte[] data64;
String base64=null;
MyRetrofitClient() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(TOKEN_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
myRetroApis = retrofit.create(RetroApis.class);
}
public String encodeToBase64() {
clientID= CLIENT_ID1;
secret=SECRET1;
String text = clientID + ":" + secret;
data64 = new byte[0];
data64 = text.getBytes(StandardCharsets.UTF_8);
base64=android.util.Base64.encodeToString(data64, android.util.Base64.DEFAULT);
return base64;
}
static Retrofit getTokenClient() {
String type="application/x-www-form-urlencoded";
String base64String=MyRetrofitClient.myRetrofitClient.encodeToBase64();
RequestBody requestBody = RequestBody.create(MediaType.parse("Content-type"), type);
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
logging.redactHeader("Authorization");
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(logging)
.addNetworkInterceptor(new Interceptor() {
#NotNull
#Override
public Response intercept(#NotNull Interceptor.Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("Authorization", MyRetrofitClient.getInstance().encodeToBase64()).build();
return chain.proceed(request);
}
})
.addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
.build();
retrofit = new Retrofit.Builder()
.baseUrl(TOKEN_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient)
.build();
return retrofit;
}
static Retrofit getTCreditReportClient(String tokenFromServer,String userPhoneNumber) {
String type="application/json";
RequestBody requestBody = RequestBody.create(MediaType.parse("Content-type"), type);
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
//logging.setLevel(HttpLoggingInterceptor.Level.BODY);
logging.redactHeader("Authorization");
logging.redactHeader("Content-type");
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(logging)
.addNetworkInterceptor(new Interceptor() {
#NotNull
#Override
public Response intercept(#NotNull Interceptor.Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("Authorization", tokenFromServer).build();
//Request request3 = chain.request().newBuilder().method("Content-type", requestBody).build();
return chain.proceed(request);
}
})
.addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.build();
retrofit = new Retrofit.Builder()
.baseUrl("https://reqres.in")
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient)
.build();
return retrofit;
}
static Retrofit getClient() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
//logging.setLevel(HttpLoggingInterceptor.Level.BODY);
logging.redactHeader("Authorization");
logging.redactHeader("Cookie");
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging)
.build();
retrofit = new Retrofit.Builder()
.baseUrl("https://reqres.in")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return retrofit;
}
public static synchronized MyRetrofitClient getInstance() {
if (myRetrofitClient == null) {
myRetrofitClient = new MyRetrofitClient();
}
return myRetrofitClient;
}
public RetroApis getMyApi() {
return myRetroApis;
}
public TokenAPI getMyTokenApi() {
return tokenAPI;
}
}
public interface TokenAPI {
String contentType= "application/json";
String authorization= "<>";
#FormUrlEncoded
#POST("client_credentials")
Call<OAuthToken> postCredentials(#Field("grant_type") String grantType);
//#FormUrlEncoded
#GET("creditScores")
Call<CreditScore> getCreditScore(#Query("client_credentials") String queryParam);
//#FormUrlEncoded
#GET("creditScores")
Call<List<CreditScoreHistory>> getCreditScoreHistory(#Query("client_credentials") String name);
#GET("/1.1/users/show.json")
Call<String> getUserDetails(#Query("screen_name") String name);
}
private void createInterSwitchTokenAPI33() {
editText = findViewById(R.id.editText);
base64EncodedCredentials = "Basic" + Base64.encodeToString ((CLIENT_ID + ":" + SECRET) .getBytes (), Base64.NO_WRAP);
//base64String=this.encodeToBase64();
String editTextInput = Objects.requireNonNull(editText.getText()).toString();
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
logging.redactHeader("Authorization");
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(logging)
.addNetworkInterceptor(new Interceptor() {
#NotNull
#Override
public okhttp3.Response intercept(#NotNull Interceptor.Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("Authorization", base64EncodedCredentials).build();
return chain.proceed(request);
}
})
.addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(TOKEN_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient)
.build();
tokenAPI = retrofit.create(TokenAPI.class);
tokenAPI.getCreditScore(editTextInput).enqueue(creditScoreCallback);
}
public void onClick(View view) {
if (view.getId() == R.id.post) {
editText = findViewById(R.id.editText);
String editTextInput = editText.getText().toString();
if (!editTextInput.isEmpty())
createCreditScoreAPI(token);
createHistoryAPI(token);
tokenAPI.getCreditScore(editTextInput).enqueue(creditScoreCallback);
tokenAPI.getCreditScoreHistory(editTextInput).enqueue(creditScoreHistoryCallback);
} else {
Toast.makeText(this, "Please provide your BVN Phone Number", Toast.LENGTH_LONG).show();
}
}
private void createCreditScoreAPI(OAuthToken token) {
OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
#NotNull
#Override
public okhttp3.Response intercept(#NotNull Chain chain) throws IOException {
//Request originalRequest = chain.request();
Request request = chain.request().newBuilder().addHeader("Authorization", token.getAccessToken()).build();
return chain.proceed(request);
}
}).addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS))
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(SCORE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
tokenAPI = retrofit.create(TokenAPI.class);
}
Callback<CreditScore> creditScoreCallback = new Callback<CreditScore>() {
#Override
public void onResponse(Call<CreditScore> call, Response<CreditScore> response) {
if (response.isSuccessful()) {
CreditScore creditScore = response.body();
if (creditScore != null) {
id = creditScore.getId();
msisdn = creditScore.getMsisdn();
score = creditScore.getScore();
dateCreated = creditScore.getDateCreated();
}
displayResponse += id + " Phone\n" + msisdn + " Score\n" + score + " dateCreated\n"+dateCreated;
txtCreditScore = findViewById(R.id.textCreditScore);
if (displayResponse == null) txtCreditScore.setText("no value");
else txtCreditScore.setText(displayResponse);
} else {
Toast.makeText(MainActivity.this, "Failure while requesting Credit Score", Toast.LENGTH_LONG).show();
txtCreditScore.setText(MessageFormat.format("Error!{0}", response.message()));
Log.d("Credit Score Details", "Code: " + response.code() + "Message: " + response.message());
}
}
#Override
public void onFailure(Call<CreditScore> call, Throwable t) {
t.printStackTrace();
}
};
private void createHistoryAPI(OAuthToken token) {
OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
#NotNull
#Override
public okhttp3.Response intercept(#NotNull Chain chain) throws IOException {
Request originalRequest = chain.request();
Request request = chain.request().newBuilder().addHeader("Authorization", token.getAccessToken()).build();
return chain.proceed(request);
}
}).addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS))
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(HISTORY_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
tokenAPI = retrofit.create(TokenAPI.class);
}
Callback<List<CreditScoreHistory>> creditScoreHistoryCallback = new Callback<List<CreditScoreHistory>>() {
#Override
public void onResponse(Call<List<CreditScoreHistory>> call, Response<List<CreditScoreHistory>> response) {
if (response.isSuccessful()) {
recyclerView = findViewById(R.id.recyclerViewHistory);
} else {
Toast.makeText(MainActivity.this, "Failure while requesting user details", Toast.LENGTH_LONG).show();
Log.d("History Callback", "Code: " + response.code() + "Message: " + response.message());
}
if (response.isSuccessful()) {
CreditScoreHistory creditScoreHistory = new CreditScoreHistory();
creditScoreList = response.body();
if (creditScoreList != null) {
for (int i = 0; i < creditScoreList.size(); i++) {
creditScoreList.add(creditScoreHistory);
count=creditScoreList.size();
}
if(creditScoreHistory !=null){
msisdn = creditScoreHistory.getMsisdn1();
score = creditScoreHistory.getScore1();
dateCreated = creditScoreHistory.getDateCreated1();
creditScoreList = creditScoreHistory.data;
}
}
displayResponse += count+" Phone\n" + msisdn + " Score\n" + score + " dateCreated\n"+dateCreated;
txtCreditHistory = findViewById(R.id.textCreditHistory);
creditHistoryAdapter = new CreditHistoryAdapter(MainActivity.this, creditScoreList);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(MainActivity.this);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(creditHistoryAdapter);
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
recyclerView.setNestedScrollingEnabled(false);
if (displayResponse == null) txtCreditScore.setText("no value");
else txtCreditHistory.setText(displayResponse);
} else {
Toast.makeText(MainActivity.this, "Failure while requesting Credit Score", Toast.LENGTH_LONG).show();
txtCreditHistory.setText(MessageFormat.format("Error!{0}", response.message()));
Log.d("Credit Score History", "Code: " + response.code() + "Message: " + response.message());
}
}
#Override
public void onFailure(Call<List<CreditScoreHistory>> call, Throwable t) {
t.printStackTrace();
}
};
buy could not get any result, please help
Use VSCode and ThunderClient extension https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client

How to write Unit Test for Hybris DAO Implementation

I am new with unit test and I am trying to test this method, but it did not manage to capture the query of the method, I only managed to get it to enter an exception but not to take the query and return it.
Is there a way to return "result.getResult().get(0)" in the unit test?
Thanks
#Override
public HouseModel findByCode(String code) {
var sQuery = "SELECT {h:pk} FROM {House as h} WHERE {h:id} = ?id ";
var query = new FlexibleSearchQuery(sQuery);
query.addQueryParameter("id", Objects.requireNonNullElse(code, ""));
SearchResult<HouseModel> result = flexibleSearchService.search(query);
return result.getResult().get(0);
}
Code Test:
#Test
public void testFindByCode() {
when(flexibleSearchService.search((FlexibleSearchQuery) any())).thenThrow(new RuntimeException("test"));
RuntimeException exception = new RuntimeException();
try {
var result2 = houseDAOImpl.findByCode("testcode");
} catch (RuntimeException e) {
e.printStackTrace();
exception = e;
}
boolean shouldtrue = exception.getMessage().equalsIgnoreCase("test");
System.out.println(exception.getMessage());
System.out.println(shouldtrue);
assertTrue(shouldtrue);
}
Hybris supports TransactionTest incase of interaction with db.
public class HouseDAOImpTest extends HybrisJUnit4TransactionalTest
{
private TypeService typeService;
private ModelService modelService;
private DeeplinkUrlDao dao;
private List<HouseModel> createdRules;
/**
* #throws java.lang.Exception
*/
#Before
public void setUp() throws Exception
{
createdRules = createHouses();
houseDAOImpl = (HouseFinderDao) Registry.getApplicationContext().getBean("houseFinderDao");
}
#Test
public void testFindByCode()
{
final HouseModel hm = houseDAOImpl.findByCode("testcode");
assertThat(hm.getCode(), is(equalTo(""testcode""));
}
private ModelService getModelService()
{
if (modelService == null)
{
modelService = (ModelService) Registry.getApplicationContext().getBean("modelService");
}
return modelService;
}
private TypeService getTypeService()
{
if (typeService == null)
{
typeService = (TypeService) Registry.getApplicationContext().getBean("typeService");
}
return typeService;
}
/**
* Creates the Houses.
*/
private List<HouseModel> createHouses()
{
final List<HouseModel> result = new ArrayList<HouseModel>();
final HouseModel houseModel1 = getModelService().create(HouseModel.class);
houseModel.setCode("testcode");
modelService.save(houseModel1);
// create other houses model and follow previous steps
result.add(houseModel1);
result.add(houseModel2);
result.add(houseModel3);
return result;
}
}

Jackson JsonNode with empty element key

I am using jackson-dataformat-xml (2.9) to parse an XML into JsonNode and then parse it to JSON
(the XML is very dynamic so that is why I am using JsonNode instead of binding to a POJO. e.g 'elementName' and 'id' names may vary).
It happens that during the JSON parsing phase, one of the element keys is empty string ("").
XML:
<elementName>
<id type="pid">abcdef123</id>
</elementName>
Parsing logic:
public Parser() {
ObjectMapper jsonMapper = new ObjectMapper();
XmlMapper xmlMapper = new XmlMapper(new XmlFactory(new WstxInputFactory()));
}
public InputStream parseXmlResponse(InputStream xmlStream) {
InputStream stream = null;
try {
JsonNode node = xmlMapper.readTree(xmlStream);
stream = new ByteArrayInputStream(jsonMapper.writer().writeValueAsBytes(node));
} catch (IOException e) {
e.printStackTrace();
}
return stream;
}
Json:
Result:
{
"elementName": {
"id": {
"type": "pid",
"": "abcdef123"
}
},
}
Expected:
{
"elementName": {
"id": {
"type": "pid",
"value": "abcdef123"
}
},
}
My idea is to find whenever I have the empty key "" and replace it with "value". Either at XML de-serialization or during JSON serialization.
I have tried to use default serializer, filter, but haven't got it working in a nice and concise way.
Suggestions are much appreciated.
Thank you for the help.
Possible Solution:
Based on #shoek suggestion I decided to write a custom serializer to avoid creating an intermediate object (ObjectNode) during the process.
edit: refactor based on the same solution proposed by #shoek.
public class CustomNode {
private JsonNode jsonNode;
public CustomNode(JsonNode jsonNode) {
this.jsonNode = jsonNode;
}
public JsonNode getJsonNode() {
return jsonNode;
}
}
public class CustomObjectsResponseSerializer extends StdSerializer<CustomNode> {
protected CustomObjectsResponseSerializer() {
super(CustomNode.class);
}
#Override
public void serialize(CustomNode node, JsonGenerator jgen, SerializerProvider provider) throws IOException {
convertObjectNode(node.getJsonNode(), jgen, provider);
}
private void convertObjectNode(JsonNode node, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeStartObject();
for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
String childName = it.next();
JsonNode childNode = node.get(childName);
// XML parser returns an empty string as value name. Replacing it with "value"
if (Objects.equals("", childName)) {
childName = "value";
}
if (childNode instanceof ArrayNode) {
jgen.writeFieldName(childName);
convertArrayNode(childNode, jgen, provider);
} else if (childNode instanceof ObjectNode) {
jgen.writeFieldName(childName);
convertObjectNode(childNode, jgen, provider);
} else {
provider.defaultSerializeField(childName, childNode, jgen);
}
}
jgen.writeEndObject();
}
private void convertArrayNode(JsonNode node, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeStartArray();
for (Iterator<JsonNode> it = node.elements(); it.hasNext(); ) {
JsonNode childNode = it.next();
if (childNode instanceof ArrayNode) {
convertArrayNode(childNode, jgen, provider);
} else if (childNode instanceof ObjectNode) {
convertObjectNode(childNode, jgen, provider);
} else {
provider.defaultSerializeValue(childNode, jgen);
}
}
jgen.writeEndArray();
}
}
You also could simply post-process the JSON DOM, traverse to all objects, and rename the keys that are empty strings to "value".
Race condition: such a key may already exist, and must not be overwritten
(e.g. <id type="pid" value="existing">abcdef123</id>).
Usage:
(note: you should not silently suppress the exception and return null, but allow it to propagate so the caller can decide to catch and apply failover logic if required)
public InputStream parseXmlResponse(InputStream xmlStream) throws IOException {
JsonNode node = xmlMapper.readTree(xmlStream);
postprocess(node);
return new ByteArrayInputStream(jsonMapper.writer().writeValueAsBytes(node));
}
Post-processing:
private void postprocess(JsonNode jsonNode) {
if (jsonNode.isArray()) {
ArrayNode array = (ArrayNode) jsonNode;
Iterable<JsonNode> elements = () -> array.elements();
// recursive post-processing
for (JsonNode element : elements) {
postprocess(element);
}
}
if (jsonNode.isObject()) {
ObjectNode object = (ObjectNode) jsonNode;
Iterable<String> fieldNames = () -> object.fieldNames();
// recursive post-processing
for (String fieldName : fieldNames) {
postprocess(object.get(fieldName));
}
// check if an attribute with empty string key exists, and rename it to 'value',
// unless there already exists another non-null attribute named 'value' which
// would be overwritten.
JsonNode emptyKeyValue = object.get("");
JsonNode existing = object.get("value");
if (emptyKeyValue != null) {
if (existing == null || existing.isNull()) {
object.set("value", emptyKeyValue);
object.remove("");
} else {
System.err.println("Skipping empty key value as a key named 'value' already exists.");
}
}
}
}
Output: just as expected.
{
"elementName": {
"id": {
"type": "pid",
"value": "abcdef123"
}
},
}
EDIT: considerations on performance:
I did a test with a large XML file (enwikiquote-20200520-pages-articles-multistream.xml, en.wikiquote XML dump, 498.4 MB), 100 rounds, with following measured times (using deltas with System.nanoTime()):
average read time (File, SSD): 2870.96 ms (JsonNode node = xmlMapper.readTree(xmlStream);)
average postprocessing time: 0.04 ms (postprocess(node);)
average write time (memory): 0.31 ms (new ByteArrayInputStream(jsonMapper.writer().writeValueAsBytes(node));)
That's a fraction of a millisecond for an object tree build from a ~500 MB file - so performance is excellent and no concern.
I figured out that this behaviour can be achieved via configuration.
Here is the kotlin code but it's simple to convert to java
Just create xmlMapper with appropriate configuration
fun jacksonCreateXmlMapper(): XmlMapper {
val module = JacksonXmlModule()
module.setXMLTextElementName("value")
return XmlMapper(module)
}
For input
<products>
<product count="5">apple</product>
<product count="10">orange</product>
</products>
you get:
{
"product" : [ {
"count" : "5",
"value" : "apple"
}, {
"count" : "10",
"value" : "orange"
} ]
}
Copying to a new ObjectNode may solve your problem.
package com.example;
import java.util.Iterator;
import java.util.Objects;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ValueNode;
public class Stackoverflow62009220 {
public static void main(String[] args) throws JsonProcessingException {
convert("{\"elementName\":{\"id\":{\"type\":\"pid\",\"\":\"abcdef123\"}}}");
convert("{\"array\":[1,99,3]}");
convert("{\"complex-array\":[null, 1, [3,7,5], {\"type\":\"pid\",\"\":\"abcdef123\"}]}");
}
private static void convert(String str) throws JsonProcessingException {
JsonNode input = (new ObjectMapper()).readTree(str);
System.out.println("in:");
System.out.println(input);
ObjectMapper mapper = new ObjectMapper();
ObjectNode obj = convertObjectNode(input, mapper);
String output = mapper.writer().writeValueAsString(obj);
System.out.println("out:");
System.out.println(output);
System.out.println("----------");
}
private static ArrayNode convertArrayNode(JsonNode current, ObjectMapper mapper) {
ArrayNode to = mapper.createArrayNode();
for (Iterator<JsonNode> it = current.elements(); it.hasNext();) {
JsonNode childNode = it.next();
if (childNode instanceof ValueNode) {
to.add(childNode);
} else if (childNode instanceof ArrayNode) {
// recurse
to.add(convertArrayNode(childNode, mapper));
} else if (childNode instanceof ObjectNode) {
to.add(convertObjectNode(childNode, mapper));
}
}
return to;
}
private static ObjectNode convertObjectNode(JsonNode current, ObjectMapper mapper) {
ObjectNode to = mapper.createObjectNode();
for (Iterator<String> it = current.fieldNames(); it.hasNext();) {
String childName = it.next();
JsonNode childNode = current.get(childName);
if (Objects.equals("", childName)) {
childName = "value";
}
if (childNode instanceof ValueNode) {
to.set(childName, childNode);
} else if (childNode instanceof ArrayNode) {
to.set(childName, convertArrayNode(childNode, mapper));
} else if (childNode instanceof ObjectNode) {
// recurse
to.set(childName, convertObjectNode(childNode, mapper));
}
}
return to;
}
}
The preceding code results in:
in:
{"elementName":{"id":{"type":"pid","":"abcdef123"}}}
out:
{"elementName":{"id":{"type":"pid","value":"abcdef123"}}}
----------
in:
{"array":[1,99,3]}
out:
{"array":[1,99,3]}
----------
in:
{"complex-array":[null,1,[3,7,5],{"type":"pid","":"abcdef123"}]}
out:
{"complex-array":[null,1,[3,7,5],{"type":"pid","value":"abcdef123"}]}
----------
P.S.
I couldn't find a way to use a custom serializer (like this) for non-typed JsonNode.
If someone knows, please post your answer. It may be a better solution with regard to memory usage/processing time.
Serializer version.
package com.example;
import java.io.IOException;
import java.util.Iterator;
import java.util.Objects;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
public class Stackoverflow62009220_B {
public static void main(String[] args) throws JsonProcessingException {
// see https://www.baeldung.com/jackson-call-default-serializer-from-custom-serializer
convert("{\"elementName\":{\"id\":{\"type\":\"pid\",\"\":\"abcdef123\"}}}");
// j = {"":"is_empty_field","num":1,"str":"aa","null_val":null,"empty_val":"","array":[3,5],"obj":{"a":"A","b":22}}
// (simple json object)
String j = "{\"\":\"is_empty_field\",\"num\":1,\"str\":\"aa\",\"null_val\":null,\"empty_val\":\"\",\"array\":[3,5],\"obj\":{\"a\":\"A\",\"b\":22}}";
convert(j);
// g = {"":"is_empty_field","num":1,"str":"aa","null_val":null,"empty_val":"","array":[3,{"":"is_empty_field","num":1,"str":"aa","null_val":null,"empty_val":"","array":[3,5],"obj":{"a":"A","b":22}}],"obj":{"":"is_empty_field","num":1,"str":"aa","null_val":null,"empty_val":"","array":[3,5],"obj":{"a":"A","b":22}}}
// (includes an array containing object j, and an object j containing array)
String g = " {\"\":\"is_empty_field\",\"num\":1,\"str\":\"aa\",\"null_val\":null,\"empty_val\":\"\",\"array\":[3,{\"\":\"is_empty_field\",\"num\":1,\"str\":\"aa\",\"null_val\":null,\"empty_val\":\"\",\"array\":[3,5],\"obj\":{\"a\":\"A\",\"b\":22}}],\"obj\":{\"\":\"is_empty_field\",\"num\":1,\"str\":\"aa\",\"null_val\":null,\"empty_val\":\"\",\"array\":[3,5],\"obj\":{\"a\":\"A\",\"b\":22}}}";
convert(g);
}
private static void convert(String str) throws JsonProcessingException {
JsonNode input = (new ObjectMapper()).readTree(str);
System.out.println("in:");
System.out.println(input);
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
SimpleSerializers serializers = new SimpleSerializers();
serializers.addSerializer(ObjectNode.class, new MyObjectNodeSerializer());
module.setSerializers(serializers);
mapper.registerModule(module);
String output = mapper.writer().writeValueAsString(input);
System.out.println("out:");
System.out.println(output);
System.out.println("----------");
}
}
class MyObjectNodeSerializer extends StdSerializer<ObjectNode> {
public MyObjectNodeSerializer() {
super(ObjectNode.class);
}
public static MyObjectNodeSerializer create() {
return new MyObjectNodeSerializer();
}
#Override
public void serialize(ObjectNode value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
for (Iterator<String> it = value.fieldNames(); it.hasNext();) {
String childName = it.next();
JsonNode childNode = value.get(childName);
if (Objects.equals("", childName)) {
childName = "value";
}
if (childNode instanceof ArrayNode) {
gen.writeFieldName(childName);
MyArrayNodeSerializer.create().serialize((ArrayNode) childNode, gen, provider);
} else if (childNode instanceof ObjectNode) {
gen.writeFieldName(childName);
this.serialize((ObjectNode) childNode, gen, provider);
} else {
provider.defaultSerializeField(childName, childNode, gen);
}
}
gen.writeEndObject();
}
}
class MyArrayNodeSerializer extends StdSerializer<ArrayNode> {
public MyArrayNodeSerializer() {
super(ArrayNode.class);
}
public static MyArrayNodeSerializer create() {
return new MyArrayNodeSerializer();
}
#Override
public void serialize(ArrayNode value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartArray();
for (Iterator<JsonNode> it = value.elements(); it.hasNext();) {
JsonNode childNode = it.next();
if (childNode instanceof ArrayNode) {
this.serialize((ArrayNode) childNode, gen, provider);
} else if (childNode instanceof ObjectNode) {
MyObjectNodeSerializer.create().serialize((ObjectNode) childNode, gen, provider);
} else {
provider.defaultSerializeValue(childNode, gen);
}
}
gen.writeEndArray();
}
}

1 request exchange, 1 reply exchange, N number responses

I want to push out 1 response and wait X milliseconds for N responses based on a correlation ID in the headers.
Current code is pretty simple: Send a call then start polling indiscriminately. That works ... for one call.
I know there is talk of a JMS solution ("JMSReader?") that spawns N number of listeners looking for correlation ID allowing these futures to time out, but I am not finding anything remotely related.
Here is a demo app that shows one way to do it...
#SpringBootApplication
public class So57377491Application {
public static void main(String[] args) {
SpringApplication.run(So57377491Application.class, args);
}
private final ConcurrentMap<String, List<String>> pending = new ConcurrentHashMap<>();
private final ConcurrentMap<String, SettableListenableFuture<List<String>>> futures = new ConcurrentHashMap<>();
#Bean
public ApplicationRunner runner(RabbitTemplate template) {
return args -> {
this.pending.put("bar", new ArrayList<>());
this.futures.put("bar", new SettableListenableFuture<>());
template.convertAndSend("so57377491", "", "Foo", msg -> {
msg.getMessageProperties().setCorrelationId("bar");
msg.getMessageProperties().setReplyTo("replyExchange/so57377491-replies");
return msg;
});
try {
List<String> list = this.futures.get("bar").get(5, TimeUnit.SECONDS);
System.out.println(list);
}
catch (TimeoutException toe) {
System.out.println("Partial result after timeout " + this.pending.remove("bar"));
}
finally {
this.futures.remove("bar");
}
};
}
#RabbitListener(bindings = #QueueBinding(
value = #Queue(value = "so57377491-1"),
exchange = #Exchange(value = "so57377491", type = "fanout")))
public String listen1(String in) {
System.out.println(in);
return in.toUpperCase();
}
#RabbitListener(bindings = #QueueBinding(
value = #Queue(value = "so57377491-2"),
exchange = #Exchange(value = "so57377491", type = "fanout")))
public String listen2(String in) {
System.out.println(in);
return in.toLowerCase();
}
#RabbitListener(bindings = #QueueBinding(
value = #Queue(value = "so57377491-3"),
exchange = #Exchange(value = "so57377491", type = "fanout")))
public String listen3(String in) {
System.out.println(in);
return in + in;
}
#RabbitListener(bindings = #QueueBinding(
value = #Queue(value = "so57377491-replies"),
exchange = #Exchange(value = "replyExchange", type = "fanout")))
public void replies(String in, #Header(AmqpHeaders.CORRELATION_ID) String correlationId) {
System.out.println(in);
List<String> list = this.pending.get(correlationId);
if (list == null) {
System.out.println("Late reply for " + correlationId);
}
else {
list.add(in);
if (list.size() == 3) {
this.futures.get(correlationId).set(list);
this.pending.remove(correlationId);
}
}
}
}
Result
Foo
Foo
Foo
foo
FOO
FooFoo
[foo, FOO, FooFoo]

VCR for ServiceStack's JsonServiceClient

The Ruby VCR library enables you to "Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests."
I'd like to create something similar using ServiceStack's JsonServiceClient, but I can't get it to work. My most recent failed attempt follows. I'd like to either make my current attempt work, or suggestions on another approach that will work.
public static class Memoization
{
public static Func<T, TResult> AsCached<T, TResult>(this Func<T, TResult> function)
{
var cachedResults = new Dictionary<T, TResult>();
string filename = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\" + (typeof(TResult)).Name + ".jsv";
var serializer = MessagePackSerializer.Create<Dictionary<T, TResult>>();
if (cachedResults.Count == 0)
{
////// load cache from file
using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
cachedResults = serializer.Unpack(fs);
}
}
return (argument) =>
{
TResult result;
lock (cachedResults)
{
if (!cachedResults.TryGetValue(argument, out result))
{
result = function(argument);
cachedResults.Add(argument, result);
////// update cache file
using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
serializer.Pack(fs, cachedResults);
}
}
}
return result;
};
}
}
class MemoizeJsonClient<TResponse> : JsonServiceClient, IServiceClient, IRestClient
{
private Func<IReturn<TResponse>, TResponse> _getCached;
private JsonServiceClient client;
public TResponse Get(IReturn<TResponse> request)
{
if (_getCached == null)
{
Func<IReturn<TResponse>, TResponse> func = GetImpl;
_getCached = func.AsCached();
}
return _getCached(request);
}
private TResponse GetImpl(IReturn<TResponse> request)
{
return client.Get(request);
}
public MemoizeJsonClient(string BaseUri) {
client = new JsonServiceClient(BaseUri);
}
}
Called like this:
[Test]
public void TestReports2()
{
string Host = "http://localhost:1337";
string BaseUri = Host + "/";
List<Options> testcases = new List<Options>();
testcases.Add(new Options("Name", "20130815", "20130815"));
foreach (Options options in testcases)
{
TransactionsReq transRequest = new TransactionsReq();
transRequest.source = "Source";
transRequest.name = new List<String>(new string[] { options.Name });
transRequest.startDate = options.StartDate;
transRequest.endDate = options.EndDate;
MemoizeJsonClient<TransactionsReqResponse> client = new MemoizeJsonClient<TransactionsReqResponse>(BaseUri);
List<Transaction> transactions;
TransactionsReqResponse transResponse = client.Get(transRequest);
transactions = transResponse.data;
}
}
But I get the following error:
System.Runtime.Serialization.SerializationException occurred
HResult=-2146233076
Message=Cannot serialize type 'ServiceStack.ServiceHost.IReturn`1[ImagineServerWrapper.DTO.TransactionsReqResponse]' because it does not have any serializable fields nor properties.
Source=MsgPack
StackTrace:
at MsgPack.Serialization.SerializerBuilder`1.CreateSerializer()
InnerException: