22

I want to create a class that can run a method until a condition about the return value is fulfilled.

It should look something like this

methodPoller.poll(pollDurationSec, pollIntervalMillis)
            .method(dog.bark())
            .until(dog -> dog.bark().equals("Woof"))
            .execute();

My method poller look somewhat like this () // following GuiSim answer

public class MethodPoller {
    Duration pollDurationSec;
    int pollIntervalMillis;


    public MethodPoller() {
    }

    public MethodPoller poll(Duration pollDurationSec, int pollIntervalMillis) {
        this.pollDurationSec = pollDurationSec;
        this.pollIntervalMillis = pollIntervalMillis;
        return this;
    }

    public <T> MethodPoller method(Supplier<T> supplier) {

        return this;
    }

    public <T> MethodPoller until(Predicate<T> predicate) {

        return this;
    }
}

But I am having a hard time going opn from here.
How can I implement a retry to a general method until a condition is fulfilled?
Thanks.

Bick
  • 17,833
  • 52
  • 146
  • 251
  • 1
    Well, regarding the method itself, you could have a reference to the target object, its reflected Method and the required arguments. You could also encapsulate it in a lambda, but it definitely depends on how that method is passed - explicitly or discovered? what about parameters? – Tassos Bassoukos Jun 22 '15 at 21:04
  • @rails If the answer I have provided satisfies you, please accept it. Do not hesitate if you have further questions. – GuiSim Jun 24 '15 at 13:08

3 Answers3

29

Yes, this can easily be done in Java 7 and even cleaner using Java 8.

The parameter to your method method should be a java.util.function.Supplier<T> and the parameter to your until method should be a java.util.function.Predicate<T>.

You can then use method references or lambda expressions to create you Poller like so:

myMethodPoller.poll(pollDurationInteger, intervalInMillisecond)
          .method(payment::getStatus)
          .until (paymentStatus -> paymentStatus.getValue().equals("COMPLETED"))
          .execute();

As a side note, if you're going to use Java 8, I'd recommend using java.time.Duration instead of an integer to represent the poll duration and the interval.

I'd also recommend looking into https://github.com/rholder/guava-retrying which is a library that you could perhaps use. If not, it could be a good inspiration for your API as it features a nice fluent API.

EDIT: Following the update to the question, here is a simple implementation. I've left some parts for you to complete as TODOs.

import java.time.Duration;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class MethodPoller<T> {

    Duration pollDurationSec;
    int pollIntervalMillis;

    private Supplier<T> pollMethod = null;
    private Predicate<T> pollResultPredicate = null;

    public MethodPoller() {
    }

    public MethodPoller<T> poll(Duration pollDurationSec, int pollIntervalMillis) {
        this.pollDurationSec = pollDurationSec;
        this.pollIntervalMillis = pollIntervalMillis;
        return this;
    }

    public MethodPoller<T> method(Supplier<T> supplier) {
        pollMethod = supplier;
        return this;
    }

    public MethodPoller<T> until(Predicate<T> predicate) {
        pollResultPredicate = predicate;
        return this;
    }

    public T execute()
    {
        // TODO: Validate that poll, method and until have been called.

        T result = null;
        boolean pollSucceeded = false;
        // TODO: Add check on poll duration
        // TODO: Use poll interval
        while (!pollSucceeded) {
            result = pollMethod.get();
            pollSucceeded = pollResultPredicate.test(result);
        }

        return result;
    }
}

Sample use:

import static org.junit.Assert.assertTrue;
import java.util.UUID;
import org.junit.Test;

public class MethodPollerTest
{

    @Test
    public void test()
    {
        MethodPoller<String> poller = new MethodPoller<>();
        String uuidThatStartsWithOneTwoThree = poller.method(() -> UUID.randomUUID().toString())
                                                     .until(s -> s.startsWith("123"))
                                                     .execute();
        assertTrue(uuidThatStartsWithOneTwoThree.startsWith("123"));
        System.out.println(uuidThatStartsWithOneTwoThree);
    }
}
GuiSim
  • 7,361
  • 6
  • 40
  • 50
  • 1
    Thanks. There are many gems in your answer. Updated the question because I still need some assistance. thanks. – Bick Jun 23 '15 at 05:59
  • May I suggest condensing the `while` loop to `while(!pollResultPredicate.test(pollMethod.get()) Thread.sleep(calculateDuration());`. – ndm13 Jun 24 '17 at 05:07
  • @ndm13 It depends. If you need to obtain the result after it has passed validation, you need to keep a reference. – GuiSim Jun 26 '17 at 00:03
  • 1
    `public MethodPoller poll` should be `public MethodPoller poll` to avoid unchecked raw types – haventchecked Sep 26 '17 at 23:08
  • Will this block the thread from processing other requests when its executing? – HarshaKA Aug 07 '18 at 05:13
  • @HarshaKA yes. If this is not desired, you can run this in a daemon thread. – GuiSim Aug 14 '18 at 20:43
22

Instead of writing this yourself, could you use Awaitility?

await()
    .atMost(3, SECONDS)
    .until(dog::bark, equalTo("woof"));
Kkkev
  • 4,716
  • 5
  • 27
  • 43
  • 2
    Even tho this works real nice `Awaitility` is better suited for testing code, not production – lcardito Aug 23 '19 at 08:38
  • @Icardito, busy wait isn't a best practice for production but if you already have such, there is no need to implement it yourself. – ozma Feb 13 '22 at 09:05
4

You can use RxJava

  Observable.interval(3, TimeUnit.SECONDS, Schedulers.io())
                .map(tick -> dog)
                .takeWhile( dog-> { return ! dog.bark().equals("Woof"); })
                .subscribe(dog ->dog.bark());


        try {
            Thread.sleep(10000);
        }catch(Exception e){}

http://blog.freeside.co/2015/01/29/simple-background-polling-with-rxjava/

frhack
  • 4,862
  • 2
  • 28
  • 25