com.moz.fiji.commons.RefreshingReference.java Source code

Java tutorial

Introduction

Here is the source code for com.moz.fiji.commons.RefreshingReference.java

Source

/**
 * (c) Copyright 2014 WibiData, Inc.
 *
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * 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 com.moz.fiji.commons;

import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import com.google.common.base.Optional;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * RefreshingReference serves as an object cache, containing a
 * reference to a single object that is periodically refreshed
 * asynchronously until closed.
 *
 * @param <T> The type that will be cached.
 */
public final class RefreshingReference<T> implements Closeable {

    private static final Logger LOG = LoggerFactory.getLogger(RefreshingReference.class);

    /**
     * Atomic reference to the cached value, which is wrapped in a WithTimestamp.
     */
    private final AtomicReference<WithTimestamp<T>> mRef;

    /**
     * Scheduler used for periodically refreshing the cached value.
     */
    private final ScheduledExecutorService mScheduler;

    /**
     * Used to initialize and refresh the cached value.
     */
    private final RefreshingLoader<T> mRefreshingLoader;

    /**
     * Used to deterministically control refresh cycles during testing.
     */
    private final Optional<LoopController> mLoopController;

    /**
     * Static factory constructor. Allows the user to pass in a name for the thread
     * the scheduler will run on.
     *
     * @param refreshPeriod Configures the refresh rate for the scheduler.
     * @param timeUnit Specifies the unit of time for the refreshPeriod.
     * @param refreshingLoader Used to initialize and refresh the cached value.
     * @param name The name of the thread the scheduler will run on.
     * @param loopController Used to deterministically control refresh cycles during testing.
     * @param <U> The type that will be cached.
     * @return A new RefreshingReference.
     */
    public static <U> RefreshingReference<U> create(final Long refreshPeriod, final TimeUnit timeUnit,
            final RefreshingLoader<U> refreshingLoader, final String name,
            final Optional<LoopController> loopController) {
        ThreadFactoryBuilder builder = new ThreadFactoryBuilder().setNameFormat(name + "-%d");
        return new RefreshingReference<>(refreshPeriod, timeUnit, refreshingLoader, builder, loopController);
    }

    /**
     * Static factory constructor. Provides a default name for the thread the scheduler will
     * run on.
     *
     * @param refreshPeriod Configures the refresh rate for the scheduler.
     * @param timeUnit Specifies the unit of time for the refreshPeriod.
     * @param refreshingLoader Used to initialize and refresh the cached value.
     * @param loopController Used to deterministically control refresh cycles during testing.
     * @param <U> The type that will be cached.
     * @return A new RefreshingReference.
     */
    public static <U> RefreshingReference<U> create(final Long refreshPeriod, final TimeUnit timeUnit,
            final RefreshingLoader<U> refreshingLoader, final Optional<LoopController> loopController) {
        ThreadFactoryBuilder builder = new ThreadFactoryBuilder().setNameFormat("refreshing-reference-%d");
        return new RefreshingReference<>(refreshPeriod, timeUnit, refreshingLoader, builder, loopController);
    }

    /**
     * Private constructor.
     *
     * @param refreshPeriod Configures the refresh rate for the scheduler.
     * @param timeUnit Specifies the unit of time for the refreshPeriod.
     * @param refreshingLoader Used to initialize and refresh the cached value.
     * @param builder Thread factory builder. Expects that the name format is already set.
     * @param loopController Used to deterministically control refresh cycles during testing.
     */
    private RefreshingReference(final Long refreshPeriod, final TimeUnit timeUnit,
            final RefreshingLoader<T> refreshingLoader, final ThreadFactoryBuilder builder,
            final Optional<LoopController> loopController) {
        mRef = new AtomicReference<>(WithTimestamp.create(refreshingLoader.initial()));
        mScheduler = Executors.newSingleThreadScheduledExecutor(builder.setDaemon(true).build());
        mRefreshingLoader = refreshingLoader;
        final RefreshingRunnable runnable = new RefreshingRunnable();
        mScheduler.scheduleAtFixedRate(runnable, refreshPeriod, refreshPeriod, timeUnit);
        mLoopController = loopController;
        ResourceTracker.get().registerResource(this);
    }

    /**
     * Runnable implementation used to refresh the reference.
     */
    private class RefreshingRunnable implements Runnable {

        /**
         * {@inheritDoc}
         *
         * Refreshes the reference. In the case of a checked exception being thrown in the
         * refresh method, the error is logged and the cache will contain the old value. If
         * refresh throws an unchecked exception, it will be logged and rethrown.
         */
        @Override
        public void run() {
            try {
                if (mLoopController.isPresent()) {
                    mLoopController.get().await();
                }
                final T value = mRef.get().getValue();
                final T newValue = mRefreshingLoader.refresh(value);
                mRef.set(WithTimestamp.create(newValue));
            } catch (Exception e) {
                LOG.error("Refresh failed.", e);
            } catch (Throwable e) {
                LOG.error("Fatal refresh exception.", e);
                throw e;
            }
        }
    }

    /**
     * Returns the current value of this reference.
     * May be an old value if the scheduler has not refreshed the value recently.
     *
     * @return The current value of this reference.
     */
    public T get() {
        return mRef.get().getValue();
    }

    /**
     * Returns the current value of this reference wrapped in a WithTimestamp class, which
     * packages the value along with the time at which the WithTimestamp object was created
     * (Measured in milliseconds since the epoch). May be an old value if the scheduler has not
     * refreshed recently.
     *
     * @return Returns a reference to the cached object wrapped in a WithTimestamp class.
     */
    public WithTimestamp<T> getWithTimestamp() {
        return mRef.get();
    }

    /**
     * Shuts down the scheduler and calls the close method in the RefreshingLoader.
     * Allows the user to demand immediate shutdown; this should only be used for testing.
     *
     * @param closeNow Will shut down immediately if this is true, otherwise will attempt to wait
     *     for concurrent execution.
     * @throws IOException If the RefreshingLoader encounters an error on closing.
     */
    public void close(Boolean closeNow) throws IOException {
        try {
            if (closeNow) {
                mScheduler.shutdownNow();
                mRefreshingLoader.close();
            } else {
                mScheduler.shutdown();
                // Await termination so that we don't close the state out from under the function
                mScheduler.awaitTermination(10, TimeUnit.SECONDS);
                mRefreshingLoader.close();
            }
        } catch (InterruptedException e) {
            Thread.interrupted();
            mRefreshingLoader.close();
        } finally {
            ResourceTracker.get().unregisterResource(this);
        }
    }

    /**
     * Shuts down the scheduler and calls the close method in the RefreshingLoader. Attempts
     * to wait for concurrent execution, but does not wait indefinitely.
     *
     * @throws IOException If the RefreshingLoader encounters an error on closing.
     */
    public void close() throws IOException {
        close(false);
    }
}