org.eclipse.milo.opcua.stack.client.transport.AbstractTransport.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.milo.opcua.stack.client.transport.AbstractTransport.java

Source

/*
 * Copyright (c) 2019 the Eclipse Milo Authors
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.milo.opcua.stack.client.transport;

import java.nio.channels.ClosedChannelException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import io.netty.channel.Channel;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import org.eclipse.milo.opcua.stack.client.UaStackClient;
import org.eclipse.milo.opcua.stack.client.UaStackClientConfig;
import org.eclipse.milo.opcua.stack.core.Stack;
import org.eclipse.milo.opcua.stack.core.StatusCodes;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.serialization.UaRequestMessage;
import org.eclipse.milo.opcua.stack.core.serialization.UaResponseMessage;
import org.eclipse.milo.opcua.stack.core.types.structured.RequestHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class AbstractTransport implements UaTransport {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final HashedWheelTimer wheelTimer;

    private final UaStackClientConfig config;

    public AbstractTransport(UaStackClientConfig config) {
        this.config = config;

        wheelTimer = config.getWheelTimer();
    }

    /**
     * Get the {@link UaStackClientConfig} of the {@link UaStackClient} this transport instance belongs to.
     *
     * @return the {@link UaStackClientConfig} of the {@link UaStackClient} this transport instance belongs to.
     */
    public UaStackClientConfig getConfig() {
        return config;
    }

    /**
     * Get a {@link Channel} suitable for sending a request on.
     * <p>
     * The Channel must have a handler capable of matching inbound responses to pending outbound
     * {@link UaTransportRequest}s.
     *
     * @return a {@link Channel} suitable for sending a request on.
     */
    public abstract CompletableFuture<Channel> channel();

    @Override
    public CompletableFuture<UaResponseMessage> sendRequest(UaRequestMessage request) {
        return channel().thenCompose(channel -> sendRequest(request, channel, true));
    }

    protected CompletableFuture<UaResponseMessage> sendRequest(UaRequestMessage request, Channel channel,
            boolean firstAttempt) {

        UaTransportRequest transportRequest = new UaTransportRequest(request);

        scheduleRequestTimeout(transportRequest);

        transportRequest.getFuture().whenComplete((response, ex) -> cancelRequestTimeout(transportRequest));

        channel.writeAndFlush(transportRequest).addListener(f -> {
            if (!f.isSuccess()) {
                Throwable cause = f.cause();

                if (cause instanceof ClosedChannelException && firstAttempt) {
                    logger.debug("Write failed, channel closed; retrying...");

                    Stack.sharedScheduledExecutor().schedule(() -> config.getExecutor().execute(() -> {
                        CompletableFuture<UaResponseMessage> sendAgain = channel()
                                .thenCompose(ch -> sendRequest(request, ch, false));

                        sendAgain.whenComplete((r, ex) -> {
                            if (r != null) {
                                transportRequest.getFuture().complete(r);
                            } else {
                                transportRequest.getFuture().completeExceptionally(ex);
                            }
                        });
                    }), 1, TimeUnit.SECONDS);
                } else {
                    transportRequest.getFuture().completeExceptionally(cause);

                    logger.debug("Write failed, request={}, requestHandle={}", request.getClass().getSimpleName(),
                            request.getRequestHeader().getRequestHandle());
                }
            } else {
                if (logger.isTraceEnabled()) {
                    logger.trace("Write succeeded for request={}, requestHandle={}",
                            request.getClass().getSimpleName(), request.getRequestHeader().getRequestHandle());
                }
            }
        });

        return transportRequest.getFuture();
    }

    private void scheduleRequestTimeout(UaTransportRequest transportRequest) {
        RequestHeader requestHeader = transportRequest.getRequest().getRequestHeader();

        long timeoutHint = requestHeader.getTimeoutHint() != null ? requestHeader.getTimeoutHint().longValue() : 0L;

        if (timeoutHint > 0) {
            Timeout timeout = wheelTimer.newTimeout(t -> {
                UaException exception = new UaException(StatusCodes.Bad_Timeout, String.format(
                        "requestId=%s timed out after %sms", requestHeader.getRequestHandle(), timeoutHint));

                transportRequest.getFuture().completeExceptionally(exception);
            }, timeoutHint, TimeUnit.MILLISECONDS);

            transportRequest.setTimeout(timeout);
        }
    }

    private void cancelRequestTimeout(UaTransportRequest transportRequest) {
        Timeout timeout = transportRequest.getTimeout();
        if (timeout != null)
            timeout.cancel();
    }

}