All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.fluxcapacitor.javaclient.scheduling.SchedulingInterceptor Maven / Gradle / Ivy

There is a newer version: 0.1015.0
Show newest version
package io.fluxcapacitor.javaclient.scheduling;

import io.fluxcapacitor.common.MessageType;
import io.fluxcapacitor.common.api.Metadata;
import io.fluxcapacitor.common.api.SerializedMessage;
import io.fluxcapacitor.common.handling.Handler;
import io.fluxcapacitor.javaclient.FluxCapacitor;
import io.fluxcapacitor.javaclient.common.Message;
import io.fluxcapacitor.javaclient.common.serialization.DeserializingMessage;
import io.fluxcapacitor.javaclient.publishing.DispatchInterceptor;
import io.fluxcapacitor.javaclient.tracking.handling.HandleSchedule;
import io.fluxcapacitor.javaclient.tracking.handling.HandlerInterceptor;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAmount;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

import static io.fluxcapacitor.common.IndexUtils.millisFromIndex;
import static io.fluxcapacitor.common.reflection.ReflectionUtils.ensureAccessible;
import static io.fluxcapacitor.common.reflection.ReflectionUtils.getAnnotatedMethods;
import static io.fluxcapacitor.javaclient.common.Message.getClock;
import static java.lang.String.format;
import static java.time.Duration.between;
import static java.time.Instant.ofEpochMilli;
import static java.time.temporal.ChronoUnit.MINUTES;
import static java.util.UUID.randomUUID;

@Slf4j
public class SchedulingInterceptor implements DispatchInterceptor, HandlerInterceptor {

    public static String scheduleIdMetadataKey = "$scheduleId";

    @Override
    public Handler wrap(Handler handler, String consumer) {
        Object target = handler.getTarget();
        List methods = getAnnotatedMethods(target, HandleSchedule.class);
        for (Method method : methods) {
            Periodic periodic = method.getAnnotation(Periodic.class);
            if (method.getParameterCount() > 0) {
                Class type = method.getParameters()[0].getType();
                if (periodic == null) {
                    periodic = type.getAnnotation(Periodic.class);
                }
                if (periodic != null) {
                    try {
                        initializePeriodicSchedule(type, periodic);
                    } catch (Exception e) {
                        log.error("Failed to initialize periodic schedule on method {}. Continuing...", method, e);
                    }
                }
            }
        }
        return HandlerInterceptor.super.wrap(handler, consumer);
    }

    protected void initializePeriodicSchedule(Class payloadType, Periodic periodic) {
        if (periodic.value() <= 0) {
            throw new IllegalStateException(format(
                    "Periodic annotation on type %s is invalid. "
                            + "Period should be a positive number of  milliseconds.", payloadType));
        }
        if (periodic.autoStart()) {
            String scheduleId = periodic.scheduleId().isEmpty() ? payloadType.getName() : periodic.scheduleId();
            if (FluxCapacitor.get().keyValueStore()
                    .storeIfAbsent("SchedulingInterceptor:initialized:" + scheduleId, true)) {
                Object payload;
                try {
                    payload = ensureAccessible(payloadType.getConstructor()).newInstance();
                } catch (Exception e) {
                    log.error("No default constructor found on @Periodic type: {}. "
                                      + "Add a public default constructor or initialize this periodic schedule by hand",
                              payloadType, e);
                    return;
                }
                FluxCapacitor.get().scheduler().schedule(
                        new Schedule(payload, scheduleId, getClock().instant().plusMillis(periodic.initialDelay())));
            }
        }
    }

    @Override
    public Function interceptDispatch(Function function,
                                                                  MessageType messageType) {
        return message -> {
            if (messageType == MessageType.SCHEDULE) {
                message.getMetadata().put(scheduleIdMetadataKey, ((Schedule) message).getScheduleId());
            }
            return function.apply(message);
        };
    }

    @Override
    public Function interceptHandling(Function function,
                                                                    Handler handler,
                                                                    String consumer) {
        return m -> {
            if (m.getMessageType() == MessageType.SCHEDULE) {
                long deadline = millisFromIndex(m.getSerializedObject().getIndex());
                Periodic periodic =
                        Optional.ofNullable(handler.getMethod(m)).map(method -> method.getAnnotation(Periodic.class))
                                .orElse(m.getPayloadClass().getAnnotation(Periodic.class));
                Object result;
                Instant now = ofEpochMilli(deadline);
                try {
                    result = function.apply(m);
                } catch (Exception e) {
                    if (periodic != null && periodic.continueOnError()) {
                        reschedule(m, now.plusMillis(periodic.value()));
                    }
                    throw e;
                }
                if (result instanceof TemporalAmount) {
                    reschedule(m, now.plus((TemporalAmount) result));
                } else if (result instanceof TemporalAccessor) {
                    reschedule(m, Instant.from((TemporalAccessor) result));
                } else if (result != null) {
                    Metadata metadata = m.getMetadata();
                    Object nextPayload = result;
                    if (result instanceof Message) {
                        metadata = ((Message) result).getMetadata();
                        nextPayload = ((Message) result).getPayload();
                    }
                    if (nextPayload != null && m.getPayloadClass().isAssignableFrom(nextPayload.getClass())) {
                        if (periodic == null) {
                            Instant dispatched = ofEpochMilli(m.getSerializedObject().getTimestamp());
                            Duration previousDelay = between(dispatched, now);
                            if (previousDelay.compareTo(Duration.ZERO) > 0) {
                                reschedule(nextPayload, metadata, now.plus(previousDelay));
                            } else {
                                log.warn("Delay between the time this schedule was created and scheduled is <= 0, "
                                                 + "rescheduling with delay of 1 minute");
                                reschedule(nextPayload, metadata, now.plus(Duration.of(1, MINUTES)));
                            }
                        } else {
                            reschedule(nextPayload, metadata, now.plusMillis(periodic.value()));
                        }
                    } else if (periodic != null) {
                        reschedule(m, now.plusMillis(periodic.value()));
                    }
                } else if (periodic != null) {
                    reschedule(m, now.plusMillis(periodic.value()));
                }
                return result;
            }
            return function.apply(m);
        };
    }

    private void reschedule(DeserializingMessage message, Instant instant) {
        reschedule(message.getPayload(), message.getMetadata(), instant);
    }

    private void reschedule(Object payload, Metadata metadata, Instant instant) {
        try {
            FluxCapacitor.get().scheduler().schedule(new Schedule(payload, metadata
                    .getOrDefault(scheduleIdMetadataKey, randomUUID().toString()), instant));
        } catch (Exception e) {
            log.error("Failed to reschedule a {}", payload.getClass(), e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy