Given two sessions (A & B) as strings, my goal is to check if A is valid in the database. If A exists, return it. If A is not provided (or does not exist in the database), I would like to proceed with checking session B in the database.
The problem with my code below is that I return regardless whether A has been found or not. Instead, I would like to check that if A is not found (is empty), proceed with checking B. Is there a way to check if a Mono does not exist?
String sessionA = "1";
String sessionB = "2";
public Mono<Session> getSession() {
if (!StringUtils.isEmpty(sessionA)) {
return myDatabaseService.findByKeyOne(sessionA); // Should only return if it exists, otherwise, check session B
}
if (!StringUtils.isEmpty(sessionB)) {
return myDatabaseService.findByAnotherSession(sessionB);
}
throw new Exception(); // If both sessions have not been found
}
// My Database Service (dummy code)
public Mono<Session> findByKeyOne(String session) {
try {
return myDatabase.find();
} catch (Exception e) {
return Mono.empty() // If an error occurrd or no result has been found, return empty
}
}
Expanding on #Eugene Botyanovsky's answer with addressing the optional sessionA, you may try something like this:
// Return an exception in case both sessionA and sessionB are empty.
if (StringUtils.isEmpty(sessionA) && StringUtils.isEmpty(sessionB)) {
return Mono.error(new Exception());
}
// simply return an empty Mono if sessionA is empty.
Mono<Session> sessionAMono = StringUtils.isEmpty(sessionA)
? Mono.empty()
: myDatabaseService.findByKeyOne(sessionA);
return sessionAMono
.switchIfEmpty(Mono.defer(() -> myDatabaseService.findByAnotherSession(sessionB)));
In this case, both scenarios when sessionA is empty, as well as when myDatabaseService.findByKeyOne(sessionA) returns an empty result (assuming that it does).
Edit:
Unrelated to your concern but I wanted to point out in your findByKeyOne function, since the myDatabase.find() is returning a Mono, wrapping it with try catch won't work since it's asynchronous. Which means you'll need to handle any potential error in the same stream. You can do something like:
return myDatabase.find()
.onErrorResume(throwable -> Mono.empty();
Try Mono#switchIfEmpty
This is as simple as:
myDatabaseService.findByKeyOne(sessionA)
.switchIfEmpty(myDatabaseService.findByAnotherSession(sessionB))
Now you only have to check your nullabilities
Related
I have this bulider method:
public static QuoteDetails quoteDetailsForMA(String response) {
handleErrors(response);
try {
FullResponse quoteDetails = extractResponse(response);
Summary summary = summaryMA(quoteDetails);
List<PenaltyElement> penalties = retrievePenalties(quoteDetails);
return QuoteDetails.builder()
.priceSummary(summary)
.penalties(penalties)
.build();
} catch (Exception e) {
LOGGER.error(
"Exception thrown response: {}",
e.getMessage());
}
}
penalties may or may not be an empty list. If it is not empty I wish to execute the return statement as it currently is(with .penalties(penalties). However, If penalties is an empty list I wish to exclude it from my return. E.g. I wish to return this:
return QuoteDetails.builder()
.priceSummary(summary)
.build();
Is this possible and if so, how is it done?
The easiest technique is to make the .penalties(penalties)
method null and empty list tolerant.
Note, the authors of both of the other answers appear to love NullPointerExceptions.
Here is some example code (with assumptions):
private List<Penalty> penaltiesList;
public QuoteDetailsBuilder penalties(final List<Penalty> newValue)
{
if (CollectionUtils.isNotEmpty(newValue))
{
penaltiesList = newValue;
}
return this;
}
CollectionUtils is an apache utility for some null-safe collection functionality.
You can do it via the isEmpty() method to see if it's empty or not:
.penalties(penalties.isEmpty() ? null : penalties)
Two "obvious" solutions come to my mind:
The builder supports an empty list in the correct way. You could maybe implement your own builder to do that which is just a wrapper around the original and doesn't call the original method penalties if the parameter is an empty list.
Use if as you would regularly do for "conditional" handling:
QuoteDetailsBuilder builder = QuoteDetails.builder()
.priceSummary(summary);
if ((null != penalties) && !penalties.isEmpty()) {
builder = builder.penalties(penalties);
}
return builder.build();
(Of course in solution #2 the name of the builder class may vary depending on the implementation.)
I've written two methods, findById searches for an item in the DB and throws an exception if the item is not found :
public Url findById(final Long id){
return urlRepository.findById(id)
.orElseThrow(() -> new ShortUrlNotFoundException("URL not found for the given ID"));
}
The second method, findByShortUrl searches for an item in the DB and uses the JPA method findByShortUrlIs which returns a List of size 1 if the item is found, there should never be more than 1 item in the DB for a given shortUrl :
public Optional<String> findByShortUrl(final String shortUrl){
List<Url> urlList = urlRepository.findByShortUrlIs(shortUrl);
if(urlList.isEmpty()){
return Optional.empty();
}
else {
return Optional.of(urlList.get(0).getLongUrl());
}
}
I like the pattern of using a ShortUrlNotFoundException if an item is not found. Should I use it also in findByShortUrl ? Then, findByShortUrl becomes:
public Optional<String> findByShortUrl(final String shortUrl){
List<Url> urlList = urlRepository.findByShortUrlIs(shortUrl);
if(urlList.isEmpty()){
throw new ShortUrlNotFoundException("URL not found for the given ID")
}
else {
return Optional.of(urlList.get(0).getLongUrl());
}
}
Why not using findFirst as this:
Optional<Url> findFirstByShortUrlIs(String shortUrl);
and then, you call:
public Optional<String> findByShortUrl(final String shortUrl){
return urlRepository.findFirstByShortUrlIs(shortUrl)
.map(Url::getLongUrl)
.map(Optional::of)
.orElseThrow(() -> new ShortUrlNotFoundException("URL not found for the given ID"));
}
Personally speaking, I would never use exception mechanism for both cases. Exceptions are designed for unexpected situations that aren't covered by conventional business logic. For example, findById should throw an exception for the search if and only if you do not expect that item in the database. The same story for the findByShortUrl. As far as I understand it's a normal case not to have a object in db, thus it should be expected case to have empty result. On other hand, you've mentioned that "there should never be more than 1 item in the DB", and that's the perfect case for an exception! If you see that return result set has more than 2 objects, so go and thrown an excpetion.
I have a REST Controller
#GetMapping("/getByClientId/{clientId}")
public ResponseEntity<Optional<List<EquityFeeds>>> getByClientId(#PathVariable("clientId") final String clientId) {
Optional<List<EquityFeeds>> cId = Optional.ofNullable(equityFeedsService.findByClientId(clientId));
System.out.println("Client Id: "+cId);
if(cId.isPresent()) {
return ResponseEntity.ok(cId);
} else {
cId.orElseThrow(() -> new ClientIdNotFoundException(clientId));
}
return ResponseEntity.ok(cId);
}
Service Class Code:
public List<EquityFeeds> findByClientId(String clientId) {
List<EquityFeeds> cId = equityFeedsRedisRepositoryImpl.findByClientId(clientId);
System.out.println("In EquityFeedService "+cId);
return cId;
}
Impl. Code (REDIS):
public List<EquityFeeds> findByClientId(String clientId) {
return (List<EquityFeeds>) listOperations.range(clientId, 0, -1);
}
Issue:
1) When the getClientId is called using a REST Controller and the clientId is not present in the REDIS Cache then:
Service class Code returns: In EquityFeedService []
The REST Controller returns: Client Id: Optional[[]]
In the REST Controller the code goes inside the if loop and displays nothing on the screen since the List is empty i.e.
if(cId.isPresent()) {
return ResponseEntity.ok(cId);
}
Why? Why cId.isPresent() returns true and the code goes inside the if loop. Ideally the code should go inside the else loop and throw an Exception since the List is empty. This is happening in case of List only it seems as my other method which has a return type of POJO doesn't have this issue.
Please help me understand this behavior and what should be done to fix this.
cId.isPresent() return true because
List<EquityFeeds> is not null , it's empty list
if(!cId.get().isEmpty()) {
return ResponseEntity.ok(cId);
} else {
throw new ClientIdNotFoundException(clientId);
}
The reason the Optional.isPresent returns true is that there is an actual value - an empty List. The Optional checks whether the value it holds is a null or not, nothing else. The isPresent checks whether the value is present inside the Optional, not inside the List itself.
So you have to treat Optional a bit different. Moreover, don't use Optional like that as substitution to the if-else constructs.
Here is a way to go:
return cId.filter(Predicate.not(List::Empty)) // if the list is not empty
.map(ResponseEntity::ok) // create a response
.orElseThrow(() -> // or else throw an exception
new ClientIdNotFoundException(clientId));
By the way, you don't want to return Optional wrapped inside the ResponseEntity. Unwrap it and return the List itself. If it is empty or null was already handled and the exception would be thrown first.
return cId.filter(Predicate.not(List::Empty))
.map(Optional::get) // exctract from the Optional
.map(ResponseEntity::ok)
.orElseThrow(() -> new ClientIdNotFoundException(clientId));
I'm communicating with an API, and I have utilised the use of the Optional class. But I feel like the error handling could be more elegant, so any suggestions on how to improve this will be well received. Also am I missing a exception handling in the actual api calls?
public Optional<Account> getGreenqloudAccount(String accountUUid) {
System.out.println("tmplog: GreenqloudAccountDao->getGreenqloudAccount");
for (Account account : apiClient.accountList()) {
if (account.getUuid().equals(accountUUid)) {
System.out.println("getGreenqloudAccount, account: " + account.toString());
return Optional.of(account);
}
}
return Optional.empty();
}
public Optional<String> getMarketplaceCustomerIdByUsername(String username) {
if (username == null || username.equals("")) {
return Optional.empty();
}
AwsMarketplace marketplaceData = apiClient.getMarketplaceData(getKeys(username));
if (marketplaceData == null) {
return Optional.empty();
}
return Optional.ofNullable(marketplaceData.getObjects().get(0).getCustomerId());
}
private Pair getKeys(String username) {
GetKeys getKeys = apiClient.getKeys(username);
return new Pair(getKeys.getApiPrivateKey(), getKeys.getApiPublicKey());
}
The main problem with your code: you throw plenty of very different outcomes into the same "bucket".
getMarketplaceCustomerIdByUsername() for example returns an empty Optional when:
the username is null
the username is "" (and think about it "" means empty, but " " isnt empty?!)
no AwsMarketplace instance can be found for the given user
As said, these are very different problems. The first one might indicate: the provided user name is bad, so you should tell your user about that. The last one means: "something is fishy, maybe the user is unknown, or something else happened".
Thus: consider to not reduce different results into an empty Optional. Rather consider to throw (different?) exceptions. You use Optional when "no result" is a valid result of the operation. But "no result, because bad user name" doesn't feel like a valid result.
If you mean handling corner cases, you could improve the code readability along with them as in the first method using findFirst such as:
public Optional<Account> getGreenqloudAccount(String accountUUid) {
System.out.println("tmplog: GreenqloudAccountDao->getGreenqloudAccount");
return apiClient.accountList().stream()
.filter(account -> account.getUuId().equals(accountUUid))
// you can 'peek' and log
.findFirst(); // you return the first account or empty
}
Moving further with the other API, notice Optional.map handles operations returning null values and returns Optional.empty for them implicitly. So you can use:
public Optional<String> getMarketplaceCustomerIdByUsername(String username) {
return Optional.ofNullable(username) // if username is null empty
.filter(name -> !name.isEmpty()) // empty string returns filtered out
.map(name -> apiClient.getMarketplaceData(getKeys(name))) // handles 'null' calue returned
.map(marketplaceData -> marketplaceData.getObjects().get(0).getCustomerId()); // here as well
}
You can use a check using Optional.isPresent()
or
use Optional.orElseThrow(Supplier<? extends X> exceptionSupplier)
Read JDK8 Doc about Optional here
Additionally, you might want to trim your input parameters before checking for empty strings
I've been trying to find a good solution to this problem for 2 hours, but I haven't found anything useful.
I have method that gets IssueData from mantisApi for use later in the code. It also catches any exceptions thrown:
try {
IssueData issue = mantisApi.getIssue(issueId, user);
task = prepareMantisTask(issue);
} catch (Exception e) {
System.out.println(e.getMessage());
}
return task;
The problem is when I get the IssueData I expect null because it might be an empty field. However when it returns, an exception is caught in this try/catch block. I tried to ignore using the following code:
public String getProjectNotNull(MantisTask mantisTask) {
if (mantisTask == null){
return null;
}
try{
String project = mantisApiCache.getProject(mantisTask.getProject()).getName();
return project;
}
catch(NullPointerException npe){
log.info("TaskService.getProjectNotNull() throws controled null");
return "-";
}
But it looks stupid when I have 20 or more records to check. Is there any other way to ignore nulls? Thank you for your time.
I'm sorry I'm at home now, and i cannot copy code.
prepareMantisTask looks like:
MantisTask mantisTask;
mantistask.setId = issue.getId();
so example. if i do mantistask.setDueData = issue.getDueData(); it is null because not all issues have this parameter. So when the debugger get to this point, it returns to
} catch (Exception e) {
System.out.println(e.getMessage());
}
and left prepareMantisTask with task null.
This two pieces of code are from different parts of my program, I just wanted to show how it works.
Why not just make a null check instead of waiting for an exception?
Perhaps something like this:
public String getProjectNotNull(MantisTask mantisTask) {
if (mantisTask == null){
return null;
}
String project = "-";
// check whatever may be null here
if (mantisTask.getProject() != null
&& mantisApiCache.getProject(mantisTask.getProject()) != null) {
project = mantisApiCache.getProject(mantisTask.getProject()).getName();
} else {
log.info("TaskService.getProjectNotNull() throws controled null")
}
return project;
}
EDIT
In response to your edit, the rule still maintains though. If not all issues have that parameter, you must check it before, so that you never get exceptions because something is null.
Assuming the null pointer exception is due to issue.getDueData(), you likewise do:
if (issue.getData() != null) {
mantistask.setDueData = issue.getDueData()
}
so that you'll never get a null pointer exception in the first case.
In short, there is no way to ignore nulls, you must check each one of them before using them (and never relying on exceptions to check for nulls).