I have the following response coming from a rest call and performing some logic based on what is returned.
This is what I want.
If the overall status code is NOT 200 OR
If within the list of SimpleResponse, none of the SimpleResponse objects has a 200 httpCode, throw an error.
The example below is incomplete.
Too much going on there, having an optional within another optional.
And then throwing the same error at 2 different places.
Also if the optional result is null say at
responseEntity.getBody(), would want to throw same error too.
Is there a cleaner way to write this?
These are the 2 related objects
#Getter
#Setter
public class SimpleResponses {
private List<SimpleResponse> simpleResponsesList;
}
#Getter
#Setter
public class SimpleResponse {
private String httpCode;
// ... other fields
}
Method calling rest call and throwing error if needed.
public ResponseEntity<SimpleResponses> get() {
HttpEntity<Object> httpEntity = this.getEntity();
// restTemplate is from Spring
ResponseEntity<SimpleResponses> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, SimpleResponses.class);
// START
// This is the logic to throw error depending on output as mentioned above.
// looking for a better way to write this.
// if none of the object inside the list has 200 code, throw error
Optional.ofNullable(responseEntity.getBody())
.map(SimpleResponses::getSimpleResponses)
.ifPresent(response -> {
Optional<SimpleResponse> simpleResponse = response.stream()
.filter(responseStream -> responseStream.getHttpCode().equals("200"))
.findAny();
if (!simpleResponse.isPresent()) {
throw new CustomRuntimeException("Failed ..... "); // repetitive same error being thrown again below.
}
});
// if overall code is not 200, throw error too
if (!responseEntity.getStatusCode().is2xxSuccessful()) {
throw new CustomRuntimeException("Failed ..... ");
}
// END
return responseEntity;
}
Using Optional::flatMap and Optional::ifPresentOrElse the following solution may be offered:
Optional.ofNullable(responseEntity.getBody())
.flatMap(body -> SimpleResponses.getSimpleResponses(body) // SimpleResponses
.getSimpleResponsesList()
.stream() // Stream<SimpleResponse>
.filter(sr -> "200".equals(sr.getHtppCode()))
.findAny() // Optional<SimpleResponse>
)
.ifPresentOrElse( // Java 9
System.out::println,
() -> { throw new CustomRuntimeException("Bad response"); }
);
For Java 8, Optional::orElseThrow may be used:
SimpleResponse good = Optional.ofNullable(responseEntity.getBody())
.flatMap(body -> SimpleResponses.getSimpleResponses(body) // SimpleResponses
.getSimpleResponsesList()
.stream() // Stream<SimpleResponse>
.filter(sr -> "200".equals(sr.getHtppCode()))
.findAny() // Optional<SimpleResponse>
)
.orElseThrow(() -> new CustomRuntimeException("Bad response"));
System.out.println(good);
Related
I am new to reactive programming and using helidon reactive java libraries in our code.
I am unable to achieve the below use case.
I have a scenario as below.
First I invoke a REST API and get response.From the response that contains list of countries I have to invoke another
REST api that retrieves the response for a country id and update the country object.
By the time I invoke second API and set value to country object as below the response is already returned.
I get use .get() and wait() on Single as it blocks the thread.
Please suggest how to overcome the below for loop and update the list of objects reactive way.
Single<WebClientResponse> singleWebClientResp = webClient.get("REST_URL");
Single<String> apiResponse = singleWebClientResponse.flatMapSingle(webClientResponse -> {
return webClientResponse.content().as(String.class);
});
apiResponse.flatMapSingle(fusionAPIResponseString -> {
List<Country> countries =
objectMapper.readValue(fusionAPIResponseString,new TypeReference<List<Country>>() {});
for (Country country : countries) {
getCountryByRegion(country.getRegion()).forSingle(newCountry -> {
LOGGER.log(Level.FINE, "newCountry ---> " + newCountry);
country.setRegion(country.getRegion() + "modified" + newCountry);
});
}
});
private Single<String> getCountryByRegion(String regionName) {
LOGGER.log(Level.FINE, "Entering getCountryByRegion");
Single<WebClientResponse> singleWebClientResponse2 = webClient.get().path("v3.1/region/" + regionName)
.contentType(MediaType.APPLICATION_JSON).request();
Single<String> retVal = singleWebClientResponse2.flatMapSingle(webClientResponse -> {
return webClientResponse.content().as(String.class);
});
LOGGER.log(Level.FINE, "Exiting getCountryByRegion");
return retVal;
}
Regards
// NOTE: this should be a static constant
GenericType<List<Country>> countriesType = new GenericType<>() {};
// NOTE: create the webClient only once, not for every request
WebClient webClient = WebClient.builder()
.addMediaSupport(JacksonSupport.create())
.baseUri("service-url")
.build();
// the pipeline starts with the initial countries (i.e. Single<List<Country>>)
webClient.get()
.path("/countries")
// get the countries as List<Country>
.request(countriesType)
// add each country to the reactive pipeline (i.e. Multi<Country>)
// to allow individual reactive mapping
.flatMap(Multi::just)
// map each country by creating a new country with new region
// use flatMap to inline the webClient result in the reactive pipeline
.flatMap(country ->
webClient.get()
.path("/region/" + country.getRegion())
.request(String.class)
.map(newCountry -> new Country(newCountry, country.getRegion())))
// aggregate all items (i.e. Single<List<Country>>)
.collectList()
.onError(res::send)
.forSingle(res::send))
If it wasn't reactive I'd do something like.
/**
* Given a refresh token and the user name, create a "payload" to represent
* secret data and store it into Redis as a hash and set it to expire in 30 seconds.
*/
Map<String, String> provideAuthenticatedData(String refreshTokenMono, String username) {
var ops = redisTemplate.opsForHash();
var payload = Map.of(
"username", username,
"secret", UUID.randomUUID().toString()
);
var puts = payload.entrySet()
.stream()
.map(e->ops.putIfAbsent(refreshToken, e.key(), e.value())
.filter(success -> !success) // finds those that have failed
.toList();
if (!puts.isEmpty()) {
throw new IllegalStateException("some elements failed to save");
}
var expireCheck = redisTemplate.expireAt(refreshToken, Instant.now().plusSeconds(30));
if (!expireCheck) {
throw new IllegalStateException("unable to expire");
}
return payload;
}
Trying to do it with Reactive it looks to get a bit messier and I got stuck after a point
/**
* Given a refresh token mono and the user name, create a "payload" to represent
* secret data and store it into Redis as a hash and set it to expire in 30 seconds.
*/
Mono<Map<String, String>> provideAuthenticatedData(Mono<String> refreshTokenMono, String username) {
var ops = reactiveRedisTemplate.opsForHash();
var payload = Map.of(
"username", username,
"secret", UUID.randomUUID().toString()
);
return refreshTokenMono
.flatMapIterable(
refreshToken -> payload.entrySet()
.stream()
.map(
e -> ops.putIfAbsent(refreshToken, e.getKey(), e.getValue())
)
.toList() // can't find an operator that would take a stream
)
// at this point I have a Flux<Mono<Boolean>>
// somehow I have to find out if any of them are false then return a Mono.error()
// then once all of it is done, set the key to expire
// finally return the payload I originally created
}
Another approach I did was this but it does not do any error handling.
Mono<Map<String, String>> provideAuthenticatedDataMono(
Mono<String> refreshTokenMono, String username) {
var ops = reactiveRedisTemplate.opsForHash();
var payload = Map.of(
"username", username,
"secret", UUID.randomUUID().toString()
);
return refreshTokenMono
.doOnNext(
refreshToken ->
payload
.entrySet()
.stream()
.map(
e -> ops.putIfAbsent(
refreshToken,
e.getKey(),
e.getValue())
)
.forEach(Mono::subscribe)
)
.doOnNext(
refreshToken ->
redisTemplate
.expireAt(
refreshToken,
Instant.now().plusSeconds(30)
)
.subscribe()
)
.flatMap((x) -> just(payload));
}
}
The main idea of reactive is to work in stream so you should avoid subscribing everywhere you should just return the Flux, Mono Stream.
THe first example is not working as you dont subscribe the mono that redis gives you.
As you are using reactive why you mix it with java stream.
one solution would be like
public Mono< YOUR_OBJECT > save(Object YOUR_OBJECT) {
return template.opsForValue().set(YOUR_OBJECT.key, YOUR_OBJECT)
.filter(aBoolean -> aBoolean)
.map(aBoolean -> YOUR_OBJECT)
.switchIfEmpty(Mono.error(new RuntimeException("Could not save data to redis")));
}
And you should be continuing the stream till the end when it will be subscribed by a controller or you
To manage swagger documentations I am using custom annotations for the methods which call the API
#SwagRef(method = POST, url = "/my/api/{pathParam1}")
public Response callMyAPI(
#MyParam(name = "pathParam1", required = true, in = PATH) String p1,
#MyParam(name = "param2", required = false, in = QUERY) String p2) {
return given()
.pathParam("pathParam1", p1)
.queryParam("param2", p2)
.get();
}
There is a separate piece of code which validates the Swagger/api/docs vs the annotations.
However I'm wondering is it possible to somehow use all this already presented data in the annotations and have a common code where I can pass the method reference or the parameter reference and the RequestSpecification can be built using the annotations.
I tried with reflection, but I'm unable to fetch the value of parameters using reflection from method
I was only able to deduce the method type and API since it's constant using the methodName and stackTrace
private SwagRef defineSwaggerInfo() {
List<StackTraceElement> stackTrace = asList(currentThread().getStackTrace());
return stackTrace.stream()
.map(tryOrNull(element -> Pair.with(element.getMethodName(), forName(element.getClassName()))))
.filter(Objects::nonNull)
.filter(pair -> MyAPI.class.isAssignableFrom(pair.getValue1()))
.map(pair -> with(pair.getValue0(), asList(pair.getValue1().getDeclaredMethods())))
.flatMap(
tryOrNull(
pair ->
pair.getValue1().stream()
.filter(method -> Objects.equals(method.getName(), pair.getValue0()))
.peek(method -> method.setAccessible(true))
.map(method -> method.getAnnotation(SwagRef.class))))
.filter(Objects::nonNull)
.findFirst()
.orElseThrow();
}
But I'm not able to come up with a generic function for Building the request spec using method parameters
I tried looking at AspectJ but wasn't able to embed it properly
There is no way to get the actual parameter values from the stack via Reflection. In fact, there’s not even a guaranty that the parameter values of an ongoing invocation are still on the stack at that point.
The closest you can get to perform automated parameter processing, is to declare the methods in an interface and generate a proxy:
interface FrontEnd {
public static FrontEnd get() {
return (FrontEnd)Proxy.newProxyInstance(FrontEnd.class.getClassLoader(),
new Class<?>[]{FrontEnd.class}, (proxy, method, args) -> {
if(method.getDeclaringClass() == Object.class) {
switch(method.getName()) {
case "toString": return
FrontEnd.class.getName()+'#'+System.identityHashCode(proxy);
case "equals": return proxy == args[0];
case "hashCode": return System.identityHashCode(proxy);
default: throw new AssertionError();
}
}
SwagRef swagRef = method.getAnnotation(SwagRef.class);
if(swagRef == null) throw new IncompatibleClassChangeError();
MyParam[] p = Arrays.stream(method.getParameterAnnotations())
.map(pa -> Arrays.stream(pa)
.filter(a -> a.annotationType() == MyParam.class)
.findFirst().orElseThrow(
() -> new IllegalStateException("missing required #MyParam")))
.toArray(MyParam[]::new);
Map<String,String> map = IntStream.range(0, args.length).boxed()
.filter(i -> p[i].required() || args[i] != null)
.collect(Collectors.toMap(i -> p[i].name(), i -> args[i].toString()));
// do actual invocation logic here
System.out.println(
"operation: "+swagRef.method()+' '+swagRef.url()+", "+map);
return null;
});
}
#SwagRef(method = POST, url = "/my/api/{pathParam1}")
public Response callMyAPI(
#MyParam(name = "pathParam1", required = true, in = PATH) String p1,
#MyParam(name = "param2", required = false, in = QUERY) String p2);
}
You may add more methods to that interface, to be handled the same way, assuming that they all have the necessary annotations.
Starting with Java 9, you can use a private method in the interface, which I would prefer here.
interface FrontEnd {
public static FrontEnd get() {
return (FrontEnd)Proxy.newProxyInstance(FrontEnd.class.getClassLoader(),
new Class<?>[]{FrontEnd.class}, FrontEnd::callImpl);
}
#SwagRef(method = POST, url = "/my/api/{pathParam1}")
public Response callMyAPI(
#MyParam(name = "pathParam1", required = true, in = PATH) String p1,
#MyParam(name = "param2", required = false, in = QUERY) String p2);
private static Object callImpl(Object proxy, Method method, Object[] args) {
if(method.getDeclaringClass() == Object.class) {
switch(method.getName()) {
case "toString": return
FrontEnd.class.getName()+'#'+System.identityHashCode(proxy);
case "equals": return proxy == args[0];
case "hashCode": return System.identityHashCode(proxy);
default: throw new AssertionError();
}
}
SwagRef swagRef = method.getAnnotation(SwagRef.class);
if(swagRef == null) throw new IncompatibleClassChangeError();
MyParam[] p = Arrays.stream(method.getParameterAnnotations())
.map(pa -> Arrays.stream(pa)
.filter(a -> a.annotationType() == MyParam.class)
.findFirst().orElseThrow(
() -> new IllegalStateException("missing required #MyParam")))
.toArray(MyParam[]::new);
Map<String,String> map = IntStream.range(0, args.length).boxed()
.filter(i -> p[i].required() || args[i] != null)
.collect(Collectors.toMap(i -> p[i].name(), i -> args[i].toString()));
// do actual invocation logic here
System.out.println("operation: "+swagRef.method()+' '+swagRef.url()+", "+map);
return null;
}
}
Alternatively, you may split up the logic between the interface and a, possibly non-public, helper class.
I have some code where I'm using Java 8 Optional in which I want to log an error when I don't get the required result.
As shown in following Example I have commented on a line where I get the error when I'm trying to log Error Message:
#PutMapping("/organs/{id}")
public Organ updateorgan(#PathVariable(value = "id") Long organId,
#Valid #RequestBody Organ organDetails) {
Organ organ = organRepository.findById(organId)
.orElseThrow(() ->
// LOG.log(Level.SEVERE,"Organ with id "+organId + "not found");
new ResourceNotFoundException("organ", "id", organId)
);
organ.setName(organDetails.getName());
Organ updatedOrgan = organRepository.save(organ);
LOG.info("Updated organ details. Response :"+updatedOrgan);
return updatedOrgan;
}
P.S - I only want to use the Java 8 method and not conventional approach.
Thanks in advance!
Make it a lambda with a body enclosed by curly braces and a return statement instead of an expression lambda:
Organ organ = organRepository.findById(organId)
.orElseThrow(() -> {
LOG.log(Level.SEVERE,"Organ with id "+organId + "not found");
return new ResourceNotFoundException("organ", "id", organId);
});
You have to use return
Organ organ = organRepository.findById(organId)
.orElseThrow(() -> {
LOG.log(Level.SEVERE,"Organ with id "+organId + "not found");
return new ResourceNotFoundException("organ", "id", organId);
});
I have 2 URLs to fetch the data, for example: \location_1\{userid} and \location_2\{userid}. for the first i get the list of the users and then need to fetch user details by above requests. the issue is that i need to call the \location_1\{userid} and in case there is an error(exception) fetch the data from \location_2\{userid}. is it possible to make it with single rx-chain? i've tried try/catch as described here but looks catch newer calls, only onErrorResumeNext calls.
Observable<List<TestModel2>> observable = apiTest
.performTest()
.flatMapIterable(items -> items)
.flatMap(testModel -> {
try
{
return apiTest.performTest2(testModel.userId);
} catch (Exception e)
{
return apiTest.performTest3(testModel.userId);
}
}).doOnNext(testModel2 -> {Log.d("TestItemData", "doOnNext --- " + testModel2.title);})
.onErrorResumeNext(throwable ->{
Log.d("TestItemData", "onErrorResumeNext -------- ");
return Observable.empty();
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Use onErrorResumeNext (as you already did a bit later in the flow):
Observable<List<TestModel2>> observable = apiTest
.performTest()
.flatMapIterable(items -> items)
.flatMap(testModel ->
apiTest.performTest2(testModel.userId)
.onErrorResumeNext(e -> apiTest.performTest3(testModel.userId)); // <----------------
)
.doOnNext(testModel2 -> {
Log.d("TestItemData", "doOnNext --- " + testModel2.title);
})
.onErrorResumeNext(throwable ->{
Log.d("TestItemData", "onErrorResumeNext -------- ");
return Observable.empty();
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());