org.springframework.data.mongodb.core.messaging.CursorReadingTask.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.mongodb.core.messaging.CursorReadingTask.java

Source

/*
 * Copyright 2018 the original author or authors.
 *
 * Licensed 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 org.springframework.data.mongodb.core.messaging;

import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.messaging.Message.MessageProperties;
import org.springframework.data.mongodb.core.messaging.SubscriptionRequest.RequestOptions;
import org.springframework.lang.Nullable;
import org.springframework.util.ErrorHandler;

import com.mongodb.client.MongoCursor;
import com.mysema.commons.lang.Assert;

/**
 * @author Christoph Strobl
 * @author Mark Paluch
 * @param <T> type of objects returned by the cursor.
 * @param <R> conversion target type.
 * @since 2.1
 */
abstract class CursorReadingTask<T, R> implements Task {

    private final Object lifecycleMonitor = new Object();

    private final MongoTemplate template;
    private final SubscriptionRequest<T, R, RequestOptions> request;
    private final Class<R> targetType;
    private final ErrorHandler errorHandler;
    private final CountDownLatch awaitStart = new CountDownLatch(1);

    private State state = State.CREATED;

    private MongoCursor<T> cursor;

    /**
     * @param template must not be {@literal null}.
     * @param request must not be {@literal null}.
     * @param targetType must not be {@literal null}.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    CursorReadingTask(MongoTemplate template, SubscriptionRequest<?, ? super T, ? extends RequestOptions> request,
            Class<R> targetType, ErrorHandler errorHandler) {

        this.template = template;
        this.request = (SubscriptionRequest) request;
        this.targetType = targetType;
        this.errorHandler = errorHandler;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Runnable
     */
    @Override
    public void run() {

        start();

        while (isRunning()) {
            try {
                T next = getNext();
                if (next != null) {
                    emitMessage(createMessage(next, targetType, request.getRequestOptions()));
                } else {
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {

                synchronized (lifecycleMonitor) {
                    state = State.CANCELLED;
                }
                Thread.interrupted();
            } catch (RuntimeException e) {

                Exception translated = template.getExceptionTranslator().translateExceptionIfPossible(e);
                Exception toHandle = translated != null ? translated : e;

                errorHandler.handleError(toHandle);
            }
        }
    }

    /**
     * Initialize the Task by 1st setting the current state to {@link State#STARTING starting} indicating the
     * initialization procedure. <br />
     * Moving on the underlying {@link MongoCursor} gets {@link #initCursor(MongoTemplate, RequestOptions) created} and is
     * {@link #isValidCursor(MongoCursor) health checked}. Once a valid {@link MongoCursor} is created the {@link #state}
     * is set to {@link State#RUNNING running}. If the health check is not passed the {@link MongoCursor} is immediately
     * {@link MongoCursor#close() closed} and a new {@link MongoCursor} is requested until a valid one is retrieved or the
     * {@link #state} changes.
     */
    private void start() {

        synchronized (lifecycleMonitor) {
            if (!State.RUNNING.equals(state)) {
                state = State.STARTING;
            }
        }

        do {

            boolean valid = false;

            synchronized (lifecycleMonitor) {

                if (State.STARTING.equals(state)) {

                    MongoCursor<T> cursor = initCursor(template, request.getRequestOptions(), targetType);
                    valid = isValidCursor(cursor);
                    if (valid) {
                        this.cursor = cursor;
                        state = State.RUNNING;
                    } else {
                        cursor.close();
                    }
                }
            }

            if (!valid) {

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {

                    synchronized (lifecycleMonitor) {
                        state = State.CANCELLED;
                    }
                    Thread.interrupted();
                }
            }
        } while (State.STARTING.equals(getState()));

        if (awaitStart.getCount() == 1) {
            awaitStart.countDown();
        }
    }

    protected abstract MongoCursor<T> initCursor(MongoTemplate template, RequestOptions options,
            Class<?> targetType);

    /*
     * (non-Javadoc)
     * @see org.springframework.data.mongodb.core.messaging.Cancelable#cancel()
     */
    @Override
    public void cancel() throws DataAccessResourceFailureException {

        synchronized (lifecycleMonitor) {

            if (State.RUNNING.equals(state) || State.STARTING.equals(state)) {
                this.state = State.CANCELLED;
                if (cursor != null) {
                    cursor.close();
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.scheduling.SchedulingAwareRunnable#isLongLived()
     */
    @Override
    public boolean isLongLived() {
        return true;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.mongodb.core.messaging.Task#getState()
     */
    @Override
    public State getState() {

        synchronized (lifecycleMonitor) {
            return state;
        }
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.mongodb.core.messaging.Task#awaitStart(java.time.Duration)
     */
    @Override
    public boolean awaitStart(Duration timeout) throws InterruptedException {

        Assert.notNull(timeout, "Timeout must not be null!");
        Assert.isFalse(timeout.isNegative(), "Timeout must not be negative!");

        return awaitStart.await(timeout.toNanos(), TimeUnit.NANOSECONDS);
    }

    protected Message<T, R> createMessage(T source, Class<R> targetType, RequestOptions options) {

        SimpleMessage<T, T> message = new SimpleMessage<>(source, source, MessageProperties.builder()
                .databaseName(template.getDb().getName()).collectionName(options.getCollectionName()).build());

        return new LazyMappingDelegatingMessage<>(message, targetType, template.getConverter());
    }

    private boolean isRunning() {
        return State.RUNNING.equals(getState());
    }

    @SuppressWarnings("unchecked")
    private void emitMessage(Message<T, R> message) {
        request.getMessageListener().onMessage((Message) message);
    }

    @Nullable
    private T getNext() {

        synchronized (lifecycleMonitor) {
            if (State.RUNNING.equals(state)) {
                return cursor.tryNext();
            }
        }

        throw new IllegalStateException(String.format("Cursor %s is not longer open.", cursor));
    }

    private static boolean isValidCursor(@Nullable MongoCursor<?> cursor) {

        if (cursor == null) {
            return false;
        }

        if (cursor.getServerCursor() == null || cursor.getServerCursor().getId() == 0) {
            return false;
        }

        return true;
    }
}