
io.fluxcapacitor.javaclient.common.ClientUtils 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.
/*
* Copyright (c) Flux Capacitor IP B.V. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fluxcapacitor.javaclient.common;
import io.fluxcapacitor.common.DefaultMemoizingBiFunction;
import io.fluxcapacitor.common.DefaultMemoizingFunction;
import io.fluxcapacitor.common.DefaultMemoizingSupplier;
import io.fluxcapacitor.common.MemoizingBiFunction;
import io.fluxcapacitor.common.MemoizingFunction;
import io.fluxcapacitor.common.MemoizingSupplier;
import io.fluxcapacitor.common.MessageType;
import io.fluxcapacitor.common.ObjectUtils;
import io.fluxcapacitor.common.handling.HandlerInvoker;
import io.fluxcapacitor.common.reflection.ReflectionUtils;
import io.fluxcapacitor.common.serialization.Revision;
import io.fluxcapacitor.javaclient.FluxCapacitor;
import io.fluxcapacitor.javaclient.modeling.SearchParameters;
import io.fluxcapacitor.javaclient.persisting.search.Searchable;
import io.fluxcapacitor.javaclient.tracking.TrackSelf;
import io.fluxcapacitor.javaclient.tracking.handling.HandleCustom;
import io.fluxcapacitor.javaclient.tracking.handling.HandleDocument;
import io.fluxcapacitor.javaclient.tracking.handling.LocalHandler;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import java.lang.reflect.Executable;
import java.lang.reflect.Parameter;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.TemporalUnit;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static io.fluxcapacitor.common.reflection.ReflectionUtils.getAnnotatedMethods;
import static io.fluxcapacitor.common.reflection.ReflectionUtils.getAnnotation;
import static io.fluxcapacitor.common.reflection.ReflectionUtils.getAnnotationAs;
import static io.fluxcapacitor.common.reflection.ReflectionUtils.getPackageAnnotation;
import static io.fluxcapacitor.common.reflection.ReflectionUtils.getTypeAnnotation;
import static java.time.temporal.ChronoUnit.DAYS;
@Slf4j
public class ClientUtils {
public static final Marker ignoreMarker = MarkerFactory.getMarker("ignoreError");
private static final BiFunction, java.lang.reflect.Executable, Optional> localHandlerCache =
memoize((target, method) -> getAnnotation(method, LocalHandler.class)
.or(() -> Optional.ofNullable(getTypeAnnotation(target, LocalHandler.class)))
.or(() -> getPackageAnnotation(target.getPackage(), LocalHandler.class))
.filter(LocalHandler::value));
private static final BiFunction, java.lang.reflect.Executable, Optional> trackSelfCache =
memoize((target, method) -> getAnnotation(method, TrackSelf.class)
.or(() -> Optional.ofNullable(getTypeAnnotation(target, TrackSelf.class)))
.or(() -> getPackageAnnotation(target.getPackage(), TrackSelf.class)));
private static final Function, SearchParameters> searchParametersCache =
memoize(type -> getAnnotationAs(type, Searchable.class, SearchParameters.class)
.map(p -> p.getCollection() == null ? p.withCollection(type.getSimpleName()) : p)
.orElseGet(() -> new SearchParameters(true, type.getSimpleName(), null, null)));
public static void waitForResults(Duration maxDuration, Collection extends Future>> futures) {
Instant deadline = Instant.now().plus(maxDuration);
for (Future> f : futures) {
try {
f.get(Math.max(0, Duration.between(Instant.now(), deadline).toMillis()), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Thread was interrupted before receiving all expected results", e);
return;
} catch (TimeoutException e) {
log.warn("Timed out before having received all expected results", e);
return;
} catch (ExecutionException ignore) {
}
}
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean isSelfTracking(Class> target, Executable method) {
return trackSelfCache.apply(target, method).isPresent();
}
public static Optional getLocalHandlerAnnotation(HandlerInvoker handlerInvoker) {
return localHandlerCache.apply(handlerInvoker.getTargetClass(), handlerInvoker.getMethod());
}
public static Optional getLocalHandlerAnnotation(Class> target,
java.lang.reflect.Executable method) {
return localHandlerCache.apply(target, method);
}
public static boolean isLocalHandler(HandlerInvoker invoker, HasMessage message) {
if (invoker.getMethod() == null) {
return false;
}
return getLocalHandlerAnnotation(invoker.getTargetClass(), invoker.getMethod()).isPresent()
|| isLocalSelfHandler(invoker, message);
}
public static boolean isLocalSelfHandler(HandlerInvoker invoker, HasMessage message) {
return isSelfHandler(invoker, message)
&& !isSelfTracking(invoker.getTargetClass(), invoker.getMethod());
}
static boolean isSelfHandler(HandlerInvoker invoker, HasMessage message) {
return Objects.equals(invoker.getTargetClass(), message.getPayloadClass());
}
@SuppressWarnings("SameParameterValue")
static boolean isTrackingHandler(Class> target, java.lang.reflect.Executable method) {
return getLocalHandlerAnnotation(target, method).map(LocalHandler::allowExternalMessages).orElse(true);
}
public static MemoizingSupplier memoize(Supplier supplier) {
return ObjectUtils.memoize(supplier);
}
public static MemoizingFunction memoize(Function supplier) {
return ObjectUtils.memoize(supplier);
}
public static MemoizingBiFunction memoize(BiFunction supplier) {
return ObjectUtils.memoize(supplier);
}
public static MemoizingSupplier memoize(Supplier supplier, Duration lifespan) {
return new DefaultMemoizingSupplier<>(supplier, lifespan, FluxCapacitor::currentClock);
}
public static MemoizingFunction memoize(Function supplier, Duration lifespan) {
return new DefaultMemoizingFunction<>(supplier, lifespan, FluxCapacitor::currentClock);
}
public static MemoizingBiFunction memoize(BiFunction supplier,
Duration lifespan) {
return new DefaultMemoizingBiFunction<>(supplier, lifespan, FluxCapacitor::currentClock);
}
public static int getRevisionNumber(Object object) {
return Optional.ofNullable(object).map(o -> o.getClass().getAnnotation(Revision.class))
.map(Revision::value).orElse(0);
}
public static String determineSearchCollection(@NonNull Object c) {
return ReflectionUtils.ifClass(c) instanceof Class> type
? getSearchParameters(type).getCollection() : c.toString();
}
public static SearchParameters getSearchParameters(Class> type) {
return searchParametersCache.apply(type);
}
public static Set getTopics(MessageType messageType, Object handler) {
return getTopics(messageType, Collections.singleton(
ReflectionUtils.ifClass(handler) instanceof Class> c ? c : handler.getClass()));
}
public static Set getTopics(MessageType messageType, Collection> handlerClasses) {
return switch (messageType) {
case DOCUMENT -> handlerClasses.stream()
.flatMap(handlerClass -> getAnnotatedMethods(handlerClass, HandleDocument.class).stream())
.flatMap(m -> ReflectionUtils.getMethodAnnotation(m, HandleDocument.class)
.map(a -> getTopic(a, m)).stream()).collect(Collectors.toSet());
case CUSTOM -> handlerClasses.stream()
.flatMap(handlerClass -> getAnnotatedMethods(handlerClass, HandleCustom.class).stream()).map(m -> {
var handleDocument = ReflectionUtils.getMethodAnnotation(
m, HandleCustom.class).filter(h -> !h.disabled());
return handleDocument.map(HandleCustom::value).orElse(null);
}).filter(Objects::nonNull).collect(Collectors.toSet());
default -> Collections.emptySet();
};
}
public static String getTopic(HandleDocument handleDocument, Executable executable) {
return Optional.ofNullable(handleDocument)
.filter(h -> !h.disabled())
.flatMap(h -> Optional.ofNullable(h.value()).filter(s -> !s.isBlank())
.or(() -> Void.class.equals(h.documentClass()) ? Optional.empty() :
Optional.of(ClientUtils.determineSearchCollection(h.documentClass()))))
.or(() -> Arrays.stream(executable.getParameters()).findFirst().map(Parameter::getType).map(
ClientUtils::determineSearchCollection))
.filter(s -> !s.isBlank()).orElse(null);
}
@SuppressWarnings("unchecked")
public static T truncate(T timestamp, TemporalUnit unit) {
T result = unit instanceof ChronoUnit chronoUnit ?
switch (chronoUnit) {
case YEARS -> (T) timestamp.with(TemporalAdjusters.firstDayOfYear());
case MONTHS -> (T) timestamp.with(TemporalAdjusters.firstDayOfMonth());
default -> timestamp;
} : timestamp;
TemporalUnit truncateUnit = unit instanceof ChronoUnit chronoUnit ?
switch (chronoUnit) {
case YEARS, MONTHS -> DAYS;
default -> chronoUnit;
} : unit;
if (result instanceof LocalDate) {
return result;
}
if (result instanceof LocalDateTime r) {
return (T) r.truncatedTo(truncateUnit);
}
if (result instanceof ZonedDateTime r) {
return (T) r.truncatedTo(truncateUnit);
}
if (result instanceof OffsetDateTime r) {
return (T) r.truncatedTo(truncateUnit);
}
if (result instanceof Instant r) {
return (T) r.truncatedTo(truncateUnit);
}
throw new UnsupportedOperationException("Unsupported temporal type: " + result.getClass());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy