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

com.digitalpetri.netty.fsm.ChannelFsm Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2024 Kevin Herron
 *
 * 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 com.digitalpetri.netty.fsm;

import com.digitalpetri.fsm.Fsm;
import com.digitalpetri.fsm.FsmContext;
import com.digitalpetri.fsm.dsl.ActionContext;
import com.digitalpetri.fsm.dsl.FsmBuilder;
import com.digitalpetri.fsm.dsl.TransitionAction;
import com.digitalpetri.netty.fsm.Event.Connect;
import com.digitalpetri.netty.fsm.Event.Disconnect;
import com.digitalpetri.netty.fsm.Event.GetChannel;
import com.digitalpetri.netty.fsm.Scheduler.Cancellable;
import io.netty.channel.Channel;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;

public class ChannelFsm {

  private final List transitionListeners = new CopyOnWriteArrayList<>();

  private final Fsm fsm;

  ChannelFsm(FsmBuilder builder, State initialState) {
    builder.addTransitionAction(new TransitionAction() {
      @Override
      public void execute(ActionContext context) {
        transitionListeners.forEach(
            listener ->
                listener.onStateTransition(context.from(), context.to(), context.event())
        );
      }

      @Override
      public boolean matches(State from, State to, Event event) {
        return true;
      }
    });

    this.fsm = builder.build(initialState);
  }

  public Fsm getFsm() {
    return fsm;
  }

  /**
   * Fire a {@link Connect} event and return a {@link CompletableFuture} that completes successfully
   * with the {@link Channel} if a successful connection is made, or already exists, and completes
   * exceptionally otherwise.
   *
   * @return a {@link CompletableFuture} that completes successfully with the {@link Channel} if a
   *     successful connection was made, or already exists, and completes exceptionally otherwise.
   */
  public CompletableFuture connect() {
    Connect connect = new Connect();

    fsm.fireEvent(connect);

    return connect.channelFuture;
  }

  /**
   * Fire a {@link Disconnect} event and return a {@link CompletableFuture} that completes
   * successfully when the {@link Channel} has been closed.
   *
   * @return a {@link CompletableFuture} that completes successfully when the {@link Channel} has
   *     been closed.
   */
  public CompletableFuture disconnect() {
    Disconnect disconnect = new Disconnect();

    fsm.fireEvent(disconnect);

    return disconnect.disconnectFuture;
  }

  /**
   * Fire a {@link GetChannel} event and return a {@link CompletableFuture} that completes
   * successfully when the {@link Channel} is available and completes exceptionally if the FSM is
   * not currently connected or the connection attempt failed.
   *
   * 

{@link #connect()} must have been called at least once before attempting to get a Channel. * Whether further calls are necessary depends on whether the FSM is configured to be persistent * in its connection attempts or not. * *

The returned CompletableFuture always fails exceptionally if the FSM is not connected. * *

This method is equivalent to {@code getChannel(true)} - if the state machine is * reconnecting it will wait for the result. * * @return a {@link CompletableFuture} that completes successfully when the {@link Channel} is * available and completes exceptionally if the FSM is not currently connected or the * connection attempt failed. */ public CompletableFuture getChannel() { return getChannel(true); } /** * Fire a {@link GetChannel} event and return a {@link CompletableFuture} that completes * successfully when the {@link Channel} is available and completes exceptionally if the FSM is * not currently connected or the connection attempt failed. * *

{@link #connect()} must have been called at least once before attempting to get a Channel. * Whether further calls are necessary depends on whether the FSM is configured to be persistent * in its connection attempts or not. * *

The returned CompletableFuture always fails exceptionally if the FSM is not connected. * * @param waitForReconnect when {@code true} and the state machine is in * {@link State#ReconnectWait} the future will not be completed until the result of the * subsequent reconnect attempt has been obtained. When {@code false} and the state machine is * in {@link State#ReconnectWait} the future is failed immediately. This parameter has no * effect in other states. * @return a {@link CompletableFuture} that completes successfully when the {@link Channel} is * available and *completes exceptionally if the FSM is not currently connected or the * connection attempt failed. */ public CompletableFuture getChannel(boolean waitForReconnect) { CompletableFuture future = fsm.getFromContext(ctx -> { State state = ctx.currentState(); if (state == State.Connected) { ConnectFuture cf = KEY_CF.get(ctx); assert cf != null; return cf.future; } else { return null; } }); if (future != null) { return future; } else { // "Slow" path... not connected yet. GetChannel getChannel = new GetChannel(waitForReconnect); fsm.fireEvent(getChannel); return getChannel.channelFuture; } } /** * Get the current {@link State} of the {@link ChannelFsm}. * * @return the current {@link State} of the {@link ChannelFsm}. */ public State getState() { return fsm.getFromContext(FsmContext::currentState); } /** * Add a {@link TransitionListener}. * * @param transitionListener the {@link TransitionListener}. */ public void addTransitionListener(TransitionListener transitionListener) { transitionListeners.add(transitionListener); } /** * Remove a previously registered {@link TransitionListener}. * * @param transitionListener the {@link TransitionListener}. */ public void removeTransitionListener(TransitionListener transitionListener) { transitionListeners.remove(transitionListener); } static final FsmContext.Key KEY_CF = new FsmContext.Key<>("connectFuture", ConnectFuture.class); static final FsmContext.Key KEY_DF = new FsmContext.Key<>("disconnectFuture", DisconnectFuture.class); static final FsmContext.Key KEY_RD = new FsmContext.Key<>("reconnectDelay", Long.class); static final FsmContext.Key KEY_RDF = new FsmContext.Key<>("reconnectDelayCancellable", Cancellable.class); static class ConnectFuture { final CompletableFuture future = new CompletableFuture<>(); } static class DisconnectFuture { final CompletableFuture future = new CompletableFuture<>(); } public interface TransitionListener { /** * A state transition has occurred. * *

Transitions may be internal, i.e. the {@code from} and {@code to} state are the same. * *

Listener notification is implemented as a {@link TransitionAction}, so take care not to * block in this callback as it will block the state machine evaluation as well. * * @param from the {@link State} transitioned from. * @param to the {@link State} transitioned to. * @param via the {@link Event} that caused the transition. */ void onStateTransition(State from, State to, Event via); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy