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

io.hekate.messaging.internal.MessageOperationAttempt Maven / Gradle / Ivy

/*
 * Copyright 2022 The Hekate Project
 *
 * The Hekate Project licenses this file to you 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.hekate.messaging.internal;

import io.hekate.cluster.ClusterNode;
import io.hekate.cluster.ClusterTopology;
import io.hekate.core.internal.util.ArgAssert;
import io.hekate.messaging.MessageMetaData;
import io.hekate.messaging.MessagingException;
import io.hekate.messaging.intercept.ClientSendContext;
import io.hekate.messaging.intercept.OutboundType;
import io.hekate.messaging.internal.MessagingProtocol.AffinityNotification;
import io.hekate.messaging.internal.MessagingProtocol.AffinityRequest;
import io.hekate.messaging.internal.MessagingProtocol.AffinitySubscribeRequest;
import io.hekate.messaging.internal.MessagingProtocol.AffinityVoidRequest;
import io.hekate.messaging.internal.MessagingProtocol.Notification;
import io.hekate.messaging.internal.MessagingProtocol.Request;
import io.hekate.messaging.internal.MessagingProtocol.RequestBase;
import io.hekate.messaging.internal.MessagingProtocol.ResponseChunk;
import io.hekate.messaging.internal.MessagingProtocol.SubscribeRequest;
import io.hekate.messaging.internal.MessagingProtocol.VoidRequest;
import io.hekate.messaging.retry.FailedAttempt;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

class MessageOperationAttempt implements ClientSendContext {
    private final MessagingClient client;

    private final ClusterTopology topology;

    private final MessageOperation operation;

    private final Optional prevFailure;

    private final MessageOperationCallback callback;

    private T payload;

    private MessageMetaData metaData;

    private Map attributes;

    private RequestHandle request;

    private boolean completed;

    public MessageOperationAttempt(
        MessagingClient client,
        ClusterTopology topology,
        MessageOperation operation,
        Optional prevFailure,
        MessageOperationCallback callback
    ) {
        this(client, topology, operation, prevFailure, callback, null, null);
    }

    private MessageOperationAttempt(
        MessagingClient client,
        ClusterTopology topology,
        MessageOperation operation,
        Optional prevFailure,
        MessageOperationCallback callback,
        MessageMetaData metaData,
        Map attributes
    ) {
        this.client = client;
        this.topology = topology;
        this.operation = operation;
        this.prevFailure = prevFailure;
        this.callback = callback;
        this.metaData = metaData;
        this.attributes = attributes;

        this.payload = operation.message();
    }

    public MessageOperationAttempt nextAttempt(Optional failure) {
        return new MessageOperationAttempt<>(client, topology, operation, failure, callback, metaData, attributes);
    }

    public void submit() {
        // Apply interceptors.
        operation.gateway().interceptors().clientSend(this);

        // Build and submit the message.
        long timeout = operation.timeout();
        boolean isRetransmit = prevFailure.isPresent();
        MessageMetaData metaData = hasMetaData() ? metaData() : null;

        MessagingConnectionOut conn = client.connection();

        switch (type()) {
            case REQUEST: {
                request = conn.registerRequest(this);

                RequestBase req;

                if (operation.hasAffinity()) {
                    req = new AffinityRequest<>(
                        operation.affinity(),
                        request.id(),
                        isRetransmit,
                        timeout,
                        payload,
                        metaData
                    );
                } else {
                    req = new Request<>(
                        request.id(),
                        isRetransmit,
                        timeout,
                        payload,
                        metaData
                    );
                }

                doSubmit(req, conn);

                break;
            }
            case SUBSCRIBE: {
                request = conn.registerRequest(this);

                RequestBase req;

                if (operation.hasAffinity()) {
                    req = new AffinitySubscribeRequest<>(
                        operation.affinity(),
                        request.id(),
                        isRetransmit,
                        timeout,
                        payload,
                        metaData
                    );
                } else {
                    req = new SubscribeRequest<>(
                        request.id(),
                        isRetransmit,
                        timeout,
                        payload,
                        metaData
                    );
                }

                doSubmit(req, conn);

                break;
            }
            case SEND_WITH_ACK: {
                request = conn.registerRequest(this);

                RequestBase req;

                if (operation.hasAffinity()) {
                    req = new AffinityVoidRequest<>(
                        operation.affinity(),
                        request.id(),
                        isRetransmit,
                        timeout,
                        payload,
                        metaData
                    );
                } else {
                    req = new VoidRequest<>(
                        request.id(),
                        isRetransmit,
                        timeout,
                        payload,
                        metaData
                    );
                }

                doSubmit(req, conn);

                break;
            }
            case SEND_NO_ACK: {
                Notification msg;

                if (operation.hasAffinity()) {
                    msg = new AffinityNotification<>(
                        operation.affinity(),
                        isRetransmit,
                        timeout,
                        payload,
                        metaData
                    );
                } else {
                    msg = new Notification<>(
                        isRetransmit,
                        timeout,
                        payload,
                        metaData
                    );
                }

                doSubmit(msg, conn);

                break;
            }
            default: {
                throw new IllegalArgumentException("Unsupported message type: " + type());
            }
        }
    }

    public void receive(ResponseChunk rsp) {
        synchronized (this) {
            if (!completed) {
                // TODO: Catch all errors.
                if (rsp == null) {
                    if (operation.type() == OutboundType.SEND_WITH_ACK) {
                        operation.gateway().interceptors().clientReceiveConfirmation(this);
                    }
                } else {
                    operation.gateway().interceptors().clientReceive(rsp);
                }

                if (callback.completeAttempt(this, rsp, null)) {
                    completed = true;

                    if (request != null) {
                        request.unregister();
                    }
                }
            }
        }
    }

    public void fail(Throwable err) {
        synchronized (this) {
            if (!completed) {
                // TODO: Catch all errors.
                operation.gateway().interceptors().clientReceiveError(err, this);

                if (callback.completeAttempt(this, null, err)) {
                    completed = true;

                    if (request != null) {
                        request.unregister();
                    }
                }
            }
        }
    }

    public boolean hasMoreAttempts() {
        if (operation.maxAttempts() < 0) {
            return true;
        } else if (operation.maxAttempts() == 0) {
            return false;
        } else {
            return prevFailure.map(prev -> prev.attempt() + 1 < operation.maxAttempts()).orElse(true);
        }
    }

    public MessagingClient client() {
        return client;
    }

    public MessageOperation operation() {
        return operation;
    }

    @Override
    public OutboundType type() {
        return operation.type();
    }

    @Override
    public T payload() {
        return operation.message();
    }

    @Override
    public String channelName() {
        return operation.gateway().name();
    }

    @Override
    public MessageMetaData metaData() {
        if (metaData == null) {
            metaData = new MessageMetaData();
        }

        return metaData;
    }

    @Override
    public void overrideMessage(T msg) {
        ArgAssert.notNull(msg, "Message");

        this.payload = msg;
    }

    @Override
    public boolean hasMetaData() {
        return metaData != null;
    }

    @Override
    public Object setAttribute(String name, Object value) {
        if (attributes == null) {
            attributes = new HashMap<>();
        }

        return attributes.put(name, value);
    }

    @Override
    public Object getAttribute(String name) {
        return attributes != null ? attributes.get(name) : null;
    }

    @Override
    public ClusterNode receiver() {
        return client.node();
    }

    @Override
    public ClusterTopology topology() {
        return topology;
    }

    @Override
    public boolean hasAffinity() {
        return operation.hasAffinity();
    }

    @Override
    public int affinity() {
        return operation.affinity();
    }

    @Override
    public Object affinityKey() {
        return operation.affinityKey();
    }

    @Override
    public Optional prevFailure() {
        return prevFailure;
    }

    private void doSubmit(Notification msg, MessagingConnectionOut conn) {
        msg.prepareSend(conn);

        conn.send(msg, (ignore, err) -> {
            if (err == null) {
                MessagingWorker worker = operation.worker();

                if (worker.isAsync()) {
                    worker.execute(() ->
                        receive(null)
                    );
                } else {
                    receive(null);
                }
            } else {
                failAsync(conn, err);
            }
        });
    }

    private void doSubmit(RequestBase req, MessagingConnectionOut conn) {
        req.prepareSend(operation.worker(), conn);

        conn.network().send(req, (msg, err) -> {
            if (err != null) {
                failAsync(conn, err);
            }
        });
    }

    private void failAsync(MessagingConnectionOut conn, Throwable err) {
        MessagingWorker worker = operation.worker();

        if (worker.isAsync()) {
            worker.execute(() ->
                fail(wrapError(conn, err))
            );
        } else {
            fail(wrapError(conn, err));
        }
    }

    private MessagingException wrapError(MessagingConnectionOut conn, Throwable err) {
        if (err instanceof MessagingException) {
            return (MessagingException)err;
        } else {
            return new MessagingException("Messaging operation failure [node=" + conn.remoteAddress() + ']', err);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy