Chaining several CompletionStage only if a condition is achieved - java-8

I have several CompletionStage methods that I'd like to chain. The problem is that the result of the first one will determine if the next ones should be executed. Right now the only way to achieve this seems to be passing "special" arguments to next CompletionStage so it doesn't execute the full code. For example:
public enum SomeResult {
RESULT_1,
RESULT_2,
RESULT_3
}
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
return CompletableFuture.supplyAsync(() -> {
// loooooong operation
if (someCondition)
return validValue;
else
return null;
}).thenCompose(result -> {
if (result != null)
return someMethodThatReturnsACompletionStage(result);
else
return CompletableFuture.completedFuture(null);
}).thenApply(result -> {
if (result == null)
return ChainingResult.RESULT_1;
else if (result.someCondition())
return ChainingResult.RESULT_2;
else
return ChainingResult.RESULT_3;
});
}
Since the whole code depends on the first someCondition (if it's false then the result will be RESULT_1, if not then the whole code should be executed) this construction looks a bit ugly to me. Is there any way to decide if 2nd (thenCompose(...)) and 3rd (thenApply(...)) methods should be executed?

You can do it like this:
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
CompletableFuture<SomeResult> shortCut = new CompletableFuture<>();
CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
// loooooong operation
if (someCondition)
withChain.complete(validValue);
else
shortCut.complete(SomeResult.RESULT_1);
});
return withChain
.thenCompose(result -> someMethodThatReturnsACompletionStage(result))
.thenApply(result ->
result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3)
.applyToEither(shortCut, Function.identity());
}
Instead of one CompletableFuture we create two, representing the different execution paths we might take. The loooooong operation is submitted as runnable then and will deliberately complete one of these CompletableFuture. The followup stages are chained to the stage representing the fulfilled condition, then both execution paths join at the last applyToEither(shortCut, Function.identity()) step.
The shortCut future has already the type of the final result and will be completed with the RESULT_1, the result of your nullpassing path, which will cause the immediate completion of the entire operation. If you don’t like the dependency between the first stage and the actual result value of the short-cut, you can retract it like this:
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
CompletableFuture<Object> shortCut = new CompletableFuture<>();
CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
// loooooong operation
if (someCondition)
withChain.complete(validValue);
else
shortCut.complete(null);
});
return withChain
.thenCompose(result -> someMethodThatReturnsACompletionStage(result))
.thenApply(result ->
result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3)
.applyToEither(shortCut.thenApply(x -> SomeResult.RESULT_1), Function.identity());
}
If your third step wasn’t exemplary but looks exactly like shown in the question, you could merge it with the code path joining step:
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
CompletableFuture<ResultOfSecondOp> shortCut = new CompletableFuture<>();
CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
// loooooong operation
if (someCondition)
withChain.complete(validValue);
else
shortCut.complete(null);
});
return withChain
.thenCompose(result -> someMethodThatReturnsACompletionStage(result))
.applyToEither(shortCut, result -> result==null? SomeResult.RESULT_1:
result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3);
}
then we only skip the second step, the someMethodThatReturnsACompletionStage invocation, but that can still stand for a long chain of intermediate steps, all skipped without the need to roll out a manual skipping via nullcheck.

For the sake of completeness I'm adding a new answer
Although the solution proposed by #Holger works great it's kinda strange to me. The solution I've been using involves separating different flows in different method calls and chaining them with thenCompose:
public enum SomeResult {
RESULT_1,
RESULT_2,
RESULT_3
}
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
return CompletableFuture.supplyAsync(() -> {
// loooooong operation
if (someCondition)
return operateWithValidValue(value);
else
return CompletableFuture.completedValue(ChainingResult.RESULT_1);
})
.thenCompose(future -> future);
public CompletionStage<SomeResult> operateWithValidValue(... value) {
// more loooong operations...
if (someCondition)
return CompletableFuture.completedValue(SomeResult.RESULT_2);
else
return doFinalOperation(someOtherValue);
}
public CompletionStage<SomeResult> doFinalOperation(... value) {
// more loooong operations...
if (someCondition)
return CompletableFuture.completedValue(SomeResult.RESULT_2);
else
return CompletableFuture.completedValue(SomeResult.RESULT_3);
}
NOTE: I've changed the algorithm from the question in sake of a more complete answer
All long operations could be potentially wrapped inside another CompletableFuture.supplyAsync with little effort

If you have to check only for null values you can solve using Optional. For example you should do:
public Bar execute(String id) {
return this.getFooById(id)
.thenCompose(this::checkFooPresent)
.thenCompose(this::doSomethingElse)
.thenCompose(this::doSomethingElseMore)
.thenApply(rankRes -> new Bar(foo));
}
private Optional<Foo> getFooById(String id) {
// some better logic to retrieve foo
return Optional.ofNullable(foo);
}
private CompletableFuture<Foo> checkFooPresent(Optional<Foo> optRanking) {
CompletableFuture<Foo> future = new CompletableFuture();
optRanking.map(future::complete).orElseGet(() -> future.completeExceptionally(new Exception("Foo not present")));
return future;
}
The checkFooPresent() receives an Optional, and if its value is null it complete exceptionally the CompletableFuture.
Obviously you need to manage that exception, but if you have previously setted an ExceptionHandler or something similar it should come for free.

Related

Completable Future inside completable future

I have couple of completable future in my code. Inside one of completable future I want another completable future (completable future inside completable future) e.g.
public CompletableFuture<List<Employee>> buildEmployee (List<EmployeeResponse> employeeInfo) {
return supplyAsync(() -> {
Map<String, List<EmployeeReward>> rewards = rewards(employeeInfo);
Map<String, List<EmployeePoints>> points = points(employeeInfo);
}, executor);
}
In above method rewards and points are two independent sequential call, I want it to parallel call for them for that I tried -
public CompletableFuture<List<Employee>> buildEmployee (List<EmployeeResponse> employeeInfo) {
return supplyAsync(() -> {
CompletableFuture<Map<String, List<EmployeeReward>>> rewards = reward(employeeInfo);
CompletableFuture<Map<String, List<EmployeePoints>>> points = points(employeeInfo);
CompletableFuture<Void> futures = allOf(rewards, points);
}, executor);
}
Is is correct way to do this? How can I improve it if not correct way?
I am building <List<Employee>> as below
employeeInfo.stream.map(employee -> Employee.builder().
.<someEmplyInfo>
.points(points.getOrDefault(employee.getEmpId, newArrayList()))
);
It is important to handle any exceptions in exceptionally block for individual futures.
Ideally, the flow of control should not be dependent on the exception handling logic, it should be wrapped in a status object that can be used to evaluate if further processing should happen.
Adding a thenApply post the allOf method and then fetching the results within the thenApply block should do the trick
public CompletableFuture<List<Employee>> buildEmployee(List<EmployeeResponse> employeeInfo) {
//Build the future instance for reward
CompletableFuture<Map<String, List<EmployeeReward>>> rewardsFuture = reward(employeeInfo)
.exceptionally(throwable -> {
//Handle the error
return null;
});
//Build the future instance for points
CompletableFuture<Map<String, List<EmployeePoints>>> pointsFuture = points(employeeInfo)
.exceptionally(throwable -> {
//Handle the error for rewards
return null;
});
return CompletableFuture.allOf(rewardsFuture, pointsFuture).thenApply(v -> {
try {
Map<String, List<EmployeeReward>> rewardsResult = rewardsFuture.get();
Map<String, List<EmployeePoints>> pointsResult = pointsFuture.get();
//Convert the above map to the desired list of string
List<Employee> buildEmployeeResult = null;
return buildEmployeeResult;
}
catch (Exception e) {
//Handle exception
return null;
}
}, executor);
}
private CompletableFuture<Map<String, List<EmployeePoints>>> points(List<EmployeeResponse> employeeInfo) {
return supplyAsync(() -> {
//Logic for points synchronous
});
}
private CompletableFuture<Map<String, List<EmployeeReward>>> reward(List<EmployeeResponse> employeeInfo) {
return supplyAsync(() -> {
//Logic for rewards synchronous
});
}
In the above approach you are using Async thread to execute buildEmployee method, which means Async thread is responsible to make two API calls rewards and points and then it will combine the result. so in the above approach this method is executing asynchronously but not the API calls.
But you can do it another way by making API calls Asynchronously, do the reward call asynchronously by using supplyAsync and then do the points call using Main thread. Finally block the main thread until async call get finished and then combine the result
public CompletableFuture<List<Employee>> buildEmployee (List<EmployeeResponse> employeeInfo) {
// First call is Async call
CompletableFuture<Map<String, List<EmployeeReward>>> rewards = CompletableFuture.supplyAsync(()->reward(employeeInfo), executor);
//Second call by main thread
Map<String, List<EmployeePoints>>> points = points(employeeInfo);
// main thread is Blocked and get the result of the future.
rewards.get(); //throws InterruptedException,ExecutionException
// Now combine the result and return list
return CompletableFuture.completedFuture(result);
}

CompletableFuture in case of if statment

I have 2 CompletableFutures and 3 methods that have to occur each after the other.
However, in some cases the second method isn't necessary.
CompletableFuture<Boolean> cf1 = supplyASync.(() ->( doSomething1()));
CompletableFuture<Boolean> cf2 = new CompletableFuture<Boolean>();
if(someThing){
cf2 = cf1.thenApplyAsync(previousResult -> doSomething2());
}
else{
cf2 = cf1;
}
if (SomeThing2) {
cf2.thenApplyAsync(previousResult ->doSomeThing3());
}
Bassicly what I'm trying to do is that doSomething2 will run after doSomething1 (if needed), but anyway that doSomeThing3 will execute after the first or both of them.
Is it the right way to do it ? Or is there better way ?
The code is being executed in sequence, that is first completable future 1, then completable future 2 (if applies) and finally task 3.
By this reason, you can use a single CompletableFuture:
CompletableFuture<Boolean> cf = CompletableFuture.supplyAsync(() -> {
if (doSomething1()) {
doSomething2();
}
doSomething3();
// return something
});
And handling the boolean results would be something like:
CompletableFuture<Boolean> cf = CompletableFuture.supplyAsync(() -> {
boolean result;
if (result = doSomething1()) {
result = doSomething2();
}
return result && doSomething3();
});
// handle future as you need, e.g. cf.get() to wait the future to end (after task 3)

JFace TreeView not launching when Input is a String

I'm trying launch a simple JFace Tree.
It's acting really strange however. When I setInput() to be a single String, the tree opens up completely blank. However, when I set input to be a String array, it works great.
This has nothing to do with the LabelProvider or ContentProvider since these behave the same no matter what (it's a really simple experimental program).
setInput() is officially allowed to take any Object. I am confused why it will not take a String, and knowing why may help me solve my other problems in life.
Setting a single String as input:
TreeViewer treeViewerLeft = new TreeViewer(shell, SWT.SINGLE);
treeViewerLeft.setLabelProvider(new TestLabelProvider());
treeViewerLeft.setContentProvider(new TestCompareContentProvider());
treeViewerLeft.expandAll();
treeViewerLeft.setInput(new String("Stooge"));
Setting an array of Strings:
TreeViewer treeViewerLeft = new TreeViewer(shell, SWT.SINGLE);
treeViewerLeft.setLabelProvider(new TestLabelProvider());
treeViewerLeft.setContentProvider(new TestCompareContentProvider());
treeViewerLeft.expandAll();
treeViewerLeft.setInput(new String[]{"Moe", "Larry", "Curly"});
The second works, and launches a tree using the following providers:
public class TestCompareContentProvider extends ArrayContentProvider implements ITreeContentProvider {
public static int children = 0;
public Object[] getChildren(Object parentElement) {
children++;
if (children > 20){
return null;
}
return new String[] {"Moe", "Larry", "Curly"};
}
public Object getParent(Object element) {
return "Parent";
}
public boolean hasChildren(Object element) {
if (children >20){
return false;
}
return true;
}
}
and
public class TestLabelProvider extends LabelProvider {
public String getText(Object element){
return "I'm something";
}
public Image getImage(Object element){
return null;
}
}
You've inherited getElements from the ArrayContentProvider and that only works with arrays. You should override this method.
I don't think you need to extend ArrayContentProvider at all.

Attempt to de-reference a null object: Salesforce

I am trying to submit data to a server where it is picked and stored in salesforce. The error I am getting is "Attempt to de-reference a null object" at the server. so I am wondering what the problem is...
Below is the sample code:
public static List<String> processAgentVisitSurvey(ProcessSurveySubmission.SurveySubmission submission, Map<String, Submission_Answer__c> answers, Person__c person) {
// Load the TDR
TDR__c tdr = loadTdr(person);
if (tdr == null) {
//Send an email saying that an unregistered person is trying to act a TDR
// Send back the error message
return new String[] { '0', 'User with handset Id ' + submission.imei + ' is not a TDR', 'SUPRESSMSG' };
}
This is the source of the error message.
There is a class the redirects to this method:
private static List<String> additionalProcessing(
SurveySubmission surveySubmission,
Survey__c survey,
Person__c interviewer,
Id intervieweeId
) {
List<String> returnValues = new List<String>();
Map<String, Submission_Answer__c> answers = parseSubmissionToMap(surveySubmission);
// Find the name of the method that this survey hooks into to do its post processing
try {
if (survey.Post_Processing_Method__c.equalsIgnoreCase('None')) {
returnValues.add('0');
returnValues.add('There is no post processing method specified for this survey');
returnValues.add('SUPRESSMSG');
}
else if (survey.Post_Processing_Method__c.equals('CKW_Registration')) {
return CkwRegistration.processCkwRegistration(answers, interviewer);
}
else if (survey.Post_Processing_Method__c.equals('CKW_Baseline')) {
return CkwRegistration.processCkwBaseline(answers, interviewer);
}
else if (survey.Post_Processing_Method__c.equals('CKW_Staff_Update')) {
return CkwRegistration.processCkwUpdate(answers, interviewer);
}
else if (survey.Post_Processing_Method__c.equals('Subcounty_Registration')) {
return CkwRegistration.processSubcounties(answers, interviewer);
}
else if (survey.Post_Processing_Method__c.equals('TDR_AGENT_VISIT')) {
return TdrHelpers.processAgentVisitSurvey(surveySubmission, answers, interviewer);
}
else if (survey.Post_Processing_Method__c.equals('UDOM_RAIN_GUAGE')) {
return UDoMSurveyProcessing.processDailyRainGauge(surveySubmission, answers, interviewer);
}
else if (survey.Post_Processing_Method__c.equals('UDOM_RAIN_GUAGE_REG')) {
return UDoMSurveyProcessing.registerRainGauge(surveySubmission, answers, interviewer);
}
else if (survey.Post_Processing_Method__c.equals('MTN_CHANNELS')) {
return MtnChannelsHelpers.processChannelsFFPSSurvey(surveySubmission, answers, interviewer);
}
else if (survey.Post_Processing_Method__c.equals('FHI_GROUP_REGISTRATION')) {
return FHISurveysHelpers.processGroupRegistration(surveySubmission, answers, interviewer, survey.Survey_Name__c);
}
else if (survey.Post_Processing_Method__c.equals('FHI_HOUSEHOLD_REGISTRATION')) {
return FHISurveysHelpers.processHouseholdRegistration(surveySubmission, answers, interviewer, survey.Survey_Name__c);
}
// else if (survey.Post_Processing_Method__c.equals('Colombia_Farmer_Registration')) {
// return ColombiaFarmerRegistrationPostProcessor.processSubmission(surveySubmission, answers, interviewer);
// }
else if (survey.Post_Processing_Method__c.equals('FIELD_OFFICER_SUPPORT')) {
return FieldOfficeHelpers.processFoSurvey(surveySubmission, answers, interviewer);
}
// else if (survey.Post_Processing_Method__c.equals('DATA_VALIDATOR_SPOT_CHECK')) {
// return DataValidatorHelpers.processSpotCheck(surveySubmission, answers, interviewer);
// }
// else if (survey.Post_Processing_Method__c.equals('DATA_VALIDATOR_BACK_CHECK')) {
// return DataValidatorHelpers.processBackCheck(surveySubmission, answers, interviewer);
// }
else if (survey.Post_Processing_Method__c.equals('EQUIPMENT_TRACKING')) {
return EquipmentTrackingHelpers.processFieldOfficerSubmission(surveySubmission, answers, interviewer);
}
}
catch (Exception e) {
returnValues.add('0');
returnValues.add(e.getMessage());
returnValues.add('An error occured. Please contact support');
}
return returnValues;
}
which I think is fine...
Please help coz I do not seem to see any problem
Thank you. I hope the code provide is enough.
Usually when I run across that error my first instinct is to look for any queries. In APEX when you return a null value (expected or not) into a single item such as Person__c person = [query that returns objects}; that error is thrown.
The SOLUTION is to ensure data gets returned into a concrete SObject such as...
List<Person__c> persons = [Here is a query or method call];
Then you would check the list with persons.size() This obeys salesforce's Bulkify everything approach they enforce as well as a more robust backend.
Sorry I couldn't provide more support, the error wasn't very evident in your code samples without a line number or debug log.
Good Luck!
Be aware: This error can also appear if you are trying to reference an uninstantiated property of a class.
Example: If you declare a List property of a class, but never instantiate it, and then attempt to add to that list, you will get this error.

refactor dilemma

I want to extract the guard statement from the following method
private void CreateProxy()
{
//extract the following guard statement.
Host selected = this.comboBox1.SelectedItem as Host;
if (selected == null)
{
return;
}
this.SearchProxy = ServiceProxy.ProxyFactory.CreateSearchProxy(GetSelectedIP().ToString());
this.StreamProxy = ServiceProxy.ProxyFactory.CreatePlayerProxy(GetSelectedIP().ToString());
}
//extracted guard method
public bool IsHostSelected()
{
Host selected = this.comboBox1.SelectedItem as Host;
if (selected == null)
{
return false;
}
return true;
}
see? now i have to add return value for the extracted method, is this kinda ugly?
any better solution to avoid adding the return value for the extracted method?
I don't see the big deal. First, I would rewrite it as:
static bool SelectedItemIsHost(ComboBox box) {
return box.SelectedItem is Host;
}
Note the rename, the ComboBox as a parameter, and the body change.
Now, this makes your code read more clearly:
void CreateProxy() {
if(SelectedItemIsHost(this.comboBox1)) {
this.SearchProxy = ServiceProxy.ProxyFactory.CreateSearchProxy(GetSelectedIP().ToString());
this.StreamProxy = ServiceProxy.ProxyFactory.CreatePlayerProxy(GetSelectedIP().ToString());
}
}
So now it reads "if the selected item is a Host then do stuff."
Now, this goes way beyond your question, but this looks like a big coupling of UI logic and domain logic. You might want to reconsider a decoupling there.
any better solution to avoid adding the return value for the extracted method?
Yes:
//extracted guard method
public bool IsHostSelected()
{
Host selected = this.comboBox1.SelectedItem as Host;
return selected != null;
}

Resources