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

org.jetlinks.supports.cluster.AbstractDeviceOperationBroker Maven / Gradle / Ivy

The newest version!
package org.jetlinks.supports.cluster;

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.core.cache.Caches;
import org.jetlinks.core.device.DeviceOperationBroker;
import org.jetlinks.core.device.DeviceStateInfo;
import org.jetlinks.core.device.ReplyFailureHandler;
import org.jetlinks.core.enums.ErrorCode;
import org.jetlinks.core.exception.DeviceOperationException;
import org.jetlinks.core.message.*;
import org.jetlinks.core.server.MessageHandler;
import org.jetlinks.core.utils.Reactors;
import org.reactivestreams.Publisher;
import org.springframework.util.StringUtils;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;

import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

@Slf4j
public abstract class AbstractDeviceOperationBroker implements DeviceOperationBroker, MessageHandler {

    private final Map> replyProcessor = Caches.newCache();

    @Override
    public abstract Flux getDeviceState(String deviceGatewayServerId, Collection deviceIdList);

    @Override
    public abstract Disposable handleGetDeviceState(String serverId, Function, Flux> stateMapper);

    @Override
    public Flux handleReply(String deviceId, String messageId, Duration timeout) {
        long startWith = System.currentTimeMillis();
        AwaitKey id = getAwaitReplyKey(deviceId, messageId);
        return replyProcessor
            .computeIfAbsent(id, ignore -> Sinks.many().multicast().onBackpressureBuffer())
            .asFlux()
            .as(flux -> {
                if (timeout.isZero() || timeout.isNegative()) {
                    return flux;
                }
                return flux.timeout(timeout, Mono.error(() -> new DeviceOperationException.NoStackTrace(ErrorCode.TIME_OUT)));
            })
            .doFinally(signal -> {
                if (AbstractDeviceOperationBroker.log.isTraceEnabled()) {
                    AbstractDeviceOperationBroker.log
                        .trace("reply device message {} {} take {}ms",
                               deviceId,
                               messageId,
                               System.currentTimeMillis() - startWith);
                }
                replyProcessor.remove(id);
                fragmentCounter.remove(id);
            });
    }

    @Override
    public abstract Mono send(String deviceGatewayServerId, Publisher message);

    @Override
    public abstract Mono send(Publisher message);

    @Override
    public abstract Flux handleSendToDeviceMessage(String serverId);

    protected abstract Mono doReply(DeviceMessageReply reply);

    private final Map fragmentCounter = new ConcurrentHashMap<>();

    protected AwaitKey getAwaitReplyKey(DeviceMessage message) {
        return getAwaitReplyKey(message.getDeviceId(), message.getMessageId());
    }

    protected AwaitKey getAwaitReplyKey(String deviceId, String messageId) {
        return new AwaitKey(deviceId,messageId);
    }

    @Override
    public Mono reply(DeviceMessageReply message) {
        if (!StringUtils.hasText(message.getMessageId())) {
            log.warn("reply message messageId is empty: {}", message);
            return Reactors.ALWAYS_FALSE;
        }

        Mono then = Reactors.ALWAYS_TRUE;
        if (message instanceof ChildDeviceMessageReply) {
            Message childDeviceMessage = ((ChildDeviceMessageReply) message).getChildDeviceMessage();
            if (childDeviceMessage instanceof DeviceMessageReply) {
                then = reply(((DeviceMessageReply) childDeviceMessage));
            }
        }
        return Mono
            .defer(() -> {
                String msgId = message.getHeader(Headers.fragmentBodyMessageId).orElse(message.getMessageId());
                if (message.getHeader(Headers.async).orElse(false)
                    || replyProcessor.containsKey(getAwaitReplyKey(message.getDeviceId(), msgId))) {
                    handleReply(message);
                    return Reactors.ALWAYS_TRUE;
                }
                return this
                    .doReply(message)
                    .thenReturn(true);
            })
            .then(then);
    }

    protected void handleReply(DeviceMessageReply message) {
        try {
            AwaitKey key = getAwaitReplyKey(message);
            String partMsgId = message.getHeader(Headers.fragmentBodyMessageId).orElse(null);
            if (partMsgId != null) {
                log.trace("handle fragment device[{}] message {}", message.getDeviceId(), message);
                AwaitKey _partMsgId = getAwaitReplyKey(message.getDeviceId(), partMsgId);
                Sinks.Many processor = replyProcessor
                    .getOrDefault(_partMsgId, replyProcessor.get(key));

                if (processor == null || processor.currentSubscriberCount() == 0) {
                    replyProcessor.remove(_partMsgId);
                    return;
                }
                int partTotal = message.getHeader(Headers.fragmentNumber).orElse(1);
                AtomicInteger counter = fragmentCounter.computeIfAbsent(_partMsgId, r -> new AtomicInteger(partTotal));

                try {
                    processor.emitNext(message, Reactors.emitFailureHandler());
                } finally {
                    if (counter.decrementAndGet() <= 0 || message.getHeader(Headers.fragmentLast).orElse(false)) {
                        try {
                            processor.tryEmitComplete();
                        } finally {
                            replyProcessor.remove(_partMsgId);
                            fragmentCounter.remove(_partMsgId);
                        }
                    }
                }
                return;
            }

            Sinks.Many processor = replyProcessor.get(key);
            if (processor != null) {
                processor.emitNext(message, Reactors.emitFailureHandler());
                processor.emitComplete(Reactors.emitFailureHandler());
            } else {
                replyProcessor.remove(key);
            }
        } catch (Throwable e) {
            replyFailureHandler.handle(e, message);
        }
    }

    @Setter
    private ReplyFailureHandler replyFailureHandler = (error, message) -> AbstractDeviceOperationBroker.log.info("unhandled reply message:{}", message, error);

    @AllArgsConstructor
    @EqualsAndHashCode(cacheStrategy = EqualsAndHashCode.CacheStrategy.LAZY)
    protected static class AwaitKey{
        private String deviceId;
        private String messageId;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy