io.fluxcapacitor.javaclient.scheduling.SchedulingInterceptor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-client Show documentation
Show all versions of java-client Show documentation
Default Java client library for interfacing with Flux Capacitor.
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);
}
}
}