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

uk.co.real_logic.artio.SessionScheduler Maven / Gradle / Ivy

The newest version!
package uk.co.real_logic.artio;

import org.agrona.ErrorHandler;
import uk.co.real_logic.artio.library.FixLibrary;
import uk.co.real_logic.artio.library.SessionConfiguration;
import uk.co.real_logic.artio.messages.SessionState;
import uk.co.real_logic.artio.session.Session;

import java.time.Clock;
import java.time.*;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

/**
 * Utility class for defining a Scheduled session. A scheduled session
 * is an initiated session that automatically logs itself in and out.
 */
public class SessionScheduler
{
    static ScheduledExecutorService timeEventPool = Executors.newScheduledThreadPool(1);

    private final ErrorHandler errorHandler;
    private final java.time.Clock clock;
    private final FixLibrary library;
    private final SessionConfiguration sessionConfiguration;
    private final Set daysOfWeek;
    private final LocalTime startTime;
    private final LocalTime endTime;

    private enum State
    {
        DISCONNECTED,
        CONNECTED,
        CONNECTING,
        DISCONNECTING
    }

    private volatile boolean duringDay;
    private State state = State.DISCONNECTED;
    private Reply reply;
    private Session session;

    /**
     * Define a session that connects non-stop on a given number of days.
     *
     * @param library the {@link FixLibrary} instance to use to connect.
     * @param sessionConfiguration the configuration for the session to use to connect.
     * @param timezoneName the name of the timezone that days start and end in.
     * @param startDayName the name of the first day of the week to connect on.
     * @param endDayName the name of the last day of the week to connect on.
     * @param errorHandler a callback for connection errors.
     * @return the SessionScheduler object.
     */
    public static SessionScheduler nonStopSession(
        final FixLibrary library,
        final SessionConfiguration sessionConfiguration,
        final String timezoneName,
        final String startDayName,
        final String endDayName,
        final ErrorHandler errorHandler)
    {
        return new SessionScheduler(
            java.time.Clock.systemDefaultZone(),
            library,
            sessionConfiguration,
            ZoneId.of(timezoneName),
            DayOfWeek.valueOf(startDayName),
            DayOfWeek.valueOf(endDayName),
            LocalTime.of(0, 0),
            LocalTime.of(0, 0),
            errorHandler);
    }

    /**
     * Define a {@link SessionScheduler} that connects between specified times on specified days.
     *
     * @param library the {@link FixLibrary} instance to use to connect.
     * @param sessionConfiguration the configuration for the session to use to connect.
     * @param timezoneName the name of the timezone that days start and end in.
     * @param startDayName the name of the first day of the week to connect on.
     * @param endDayName the name of the last day of the week to connect on.
     * @param startTimeConfiguration the time to start a connection to the exchange.
     * @param endTimeConfiguration the time to end a connection to the exchange.
     * @param errorHandler a callback for connection errors.
     * @return the SessionScheduler object.
     */
    public static SessionScheduler fromRawConfig(
        final FixLibrary library,
        final SessionConfiguration sessionConfiguration,
        final String timezoneName,
        final String startDayName,
        final String endDayName,
        final String startTimeConfiguration,
        final String endTimeConfiguration,
        final ErrorHandler errorHandler)
    {
        return new SessionScheduler(
            java.time.Clock.systemDefaultZone(),
            library,
            sessionConfiguration,
            ZoneId.of(timezoneName),
            DayOfWeek.valueOf(startDayName),
            DayOfWeek.valueOf(endDayName),
            LocalTime.parse(startTimeConfiguration),
            LocalTime.parse(endTimeConfiguration),
            errorHandler);
    }

    /**
     * Define a {@link SessionScheduler} that connects between specified times on specified days.
     *
     * @param clock the clock to use as a source of time.
     * @param library the {@link FixLibrary} instance to use to connect.
     * @param sessionConfiguration the configuration for the session to use to connect.
     * @param timezone the timezone that days start and end in.
     * @param startDay the first day of the week to connect on.
     * @param endDay the name of the last day of the week to connect on.
     * @param startTime the time to start a connection to the exchange.
     * @param endTime the time to end a connection to the exchange.
     * @param errorHandler a callback for connection errors.
     */
    public SessionScheduler(
        final Clock clock,
        final FixLibrary library,
        final SessionConfiguration sessionConfiguration,
        final ZoneId timezone,
        final DayOfWeek startDay,
        final DayOfWeek endDay,
        final LocalTime startTime,
        final LocalTime endTime,
        final ErrorHandler errorHandler)
    {
        this.clock = clock.withZone(timezone);
        this.library = library;
        this.sessionConfiguration = sessionConfiguration;
        this.startTime = startTime;
        this.endTime = endTime;
        this.errorHandler = errorHandler;

        daysOfWeek = Arrays
            .stream(DayOfWeek.values())
            .filter(dayOfWeek -> dayOfWeek.compareTo(startDay) >= 0 && dayOfWeek.compareTo(endDay) <= 0)
            .collect(Collectors.toSet());

        scheduleStart();
    }

    /**
     * Getter for whether you are currently during your normally connected working day.
     *
     * @return whether you are currently during your normally connected working day.
     */
    public boolean duringDay()
    {
        return duringDay;
    }

    /**
     * Duty cycle - to be polled on the same thread as the {@link FixLibrary}.
     *
     * @return the number of ites of work done.
     */
    public int doWork()
    {
        switch (state)
        {
            case DISCONNECTED:
            {
                if (duringDay)
                {
                    reply = library.initiate(sessionConfiguration);
                    state = State.CONNECTING;
                    return 1;
                }

                return 0;
            }

            case CONNECTING:
            {
                switch (reply.state())
                {
                    case COMPLETED:
                    {
                        session = reply.resultIfPresent();
                        state = State.CONNECTED;
                        scheduleEnd();
                        return 1;
                    }

                    case ERRORED:
                    {
                        errorHandler.onError(reply.error());
                        reply = null;
                        state = State.DISCONNECTED;
                        return 1;
                    }

                    case TIMED_OUT:
                    {
                        errorHandler.onError(new TimeoutException("Timeout connected to Session"));
                        reply = null;
                        state = State.DISCONNECTED;
                        return 1;
                    }

                    default:
                        break;
                }

                return 0;
            }

            case CONNECTED:
            {
                if (!duringDay)
                {
                    final long position = session.logoutAndDisconnect();
                    if (position > 0)
                    {
                        state = State.DISCONNECTING;
                    }

                    return 1;
                }
                else if (!session.isConnected())
                {
                    session = null;
                    state = State.DISCONNECTED;
                    return 1;
                }

                return 0;
            }

            case DISCONNECTING:
            {
                if (session.state() == SessionState.DISCONNECTED)
                {
                    session = null;
                    state = State.DISCONNECTED;
                    return 1;
                }

                return 0;
            }
        }

        return 0;
    }

    private void scheduleStart()
    {
        final ZonedDateTime now = ZonedDateTime.now(clock);
        if (daysOfWeek.contains(now.getDayOfWeek()))
        {
            final LocalTime currentTime = now.toLocalTime();
            final long startGapMs = Duration.between(currentTime, startTime).toMillis();
            if (startGapMs <= 0)
            {
                final long endGapMs = Duration.between(currentTime, endTime).toMillis();
                if (endGapMs > 0)
                {
                    onDayStart();
                }
            }
            else
            {
                scheduleEvent(startGapMs, this::onDayStart);
            }
        }
    }

    private void scheduleEnd()
    {
        final ZonedDateTime now = ZonedDateTime.now(clock);
        if (daysOfWeek.contains(now.getDayOfWeek()))
        {
            final LocalTime currentTime = now.toLocalTime();
            final long endGapMs = Duration.between(currentTime, endTime).toMillis();
            if (endGapMs <= 0)
            {
                onDayEnd();
            }
            else
            {
                scheduleEvent(endGapMs, this::onDayEnd);
            }
        }
    }

    private void scheduleEvent(final long gapMs, final Runnable command)
    {
        timeEventPool.schedule(command, gapMs, TimeUnit.MILLISECONDS);
    }

    void onDayStart()
    {
        duringDay = true;
    }

    void onDayEnd()
    {
        duringDay = false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy