/* * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) * * This software is dual-licensed under: * * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any * later version; * - the Apache Software License (ASL) version 2.0. * * The text of this file and of both licenses is available at the root of this * project or, if you have the jar distribution, in directory META-INF/, under * the names LGPL-3.0.txt and ASL-2.0.txt respectively. * * Direct link to the sources: * * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt */ package com.github.fge.msgsimple.provider; import com.github.fge.msgsimple.InternalBundle; import com.github.fge.msgsimple.source.MessageSource; import javax.annotation.concurrent.ThreadSafe; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; /** * A caching, on-demand loading message source provider with configurable expiry * *
This class uses a {@link MessageSourceLoader} internally to look up * message sources. As is the case for {@link StaticMessageSourceProvider}, you * can also set a default source if the loader fails to grab a source.
* *Apart from the loader, you can customize two aspects of the provider:
* *Note that the expiry time is periodic only, and not per source. The * loading result (success or failure) is recorded permanently until the expiry * time kicks in.
* *In the event of a timeout, the task remains active until it gets a result; * this means, for instance, that if you set up a timeout of 500 milliseconds, * but the task takes 2 seconds to complete, during these two seconds, the * default source will be returned instead.
* *You can also configure a loader so that it never expires.
* *You cannot instantiate that class directly; use {@link #newBuilder()} to * obtain a builder class and set up your provider.
* * @see Builder */ @ThreadSafe public final class LoadingMessageSourceProvider implements MessageSourceProvider { /* * Use daemon threads. We don't give control to the user about the * ExecutorService, and we don't have a reliable way to shut it down (a JVM * shutdown hook does not get involved on a webapp shutdown, so we cannot * use that...). */ private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() { private final ThreadFactory factory = Executors.defaultThreadFactory(); @Override public Thread newThread(final Runnable r) { final Thread ret = factory.newThread(r); ret.setDaemon(true); return ret; } }; private static final InternalBundle BUNDLE = InternalBundle.getInstance(); private static final int NTHREADS = 3; /* * Executor service for loading tasks */ private final ExecutorService service = Executors.newFixedThreadPool(NTHREADS, THREAD_FACTORY); /* * Loader and default source */ private final MessageSourceLoader loader; private final MessageSource defaultSource; /* * Timeout */ private final long timeoutDuration; private final TimeUnit timeoutUnit; /* * Expiry * * Note that expiry is set up, if necessary, in the first call to * .getMessage(). */ private final AtomicBoolean expiryEnabled; private final long expiryDuration; private final TimeUnit expiryUnit; /* * List of sources */ private final MapIf the loader passed as an argument fails to load a message * source after the specified timeout is elapsed, then the default * messagesource will be returned (if any).
* * @param duration number of units * @param unit the time unit * @throws IllegalArgumentException {@code duration} is negative or zero * @throws NullPointerException {@code unit} is null * @return this * * @see #setLoader(MessageSourceLoader) * @see #setDefaultSource(MessageSource) */ public Builder setLoadTimeout(final long duration, final TimeUnit unit) { BUNDLE.checkArgument(duration > 0L, "cfg.nonPositiveDuration"); BUNDLE.checkNotNull(unit, "cfg.nullTimeUnit"); timeoutDuration = duration; timeoutUnit = unit; return this; } /** * Set the source expiry time (10 minutes by default) * *Do not use this method if you want no expiry at all; use * {@link #neverExpires()} instead.
* * @since 0.5 * * @param duration number of units * @param unit the time unit * @throws IllegalArgumentException {@code duration} is negative or zero * @throws NullPointerException {@code unit} is null * @return this */ public Builder setExpiryTime(final long duration, final TimeUnit unit) { BUNDLE.checkArgument(duration > 0L, "cfg.nonPositiveDuration"); BUNDLE.checkNotNull(unit, "cfg.nullTimeUnit"); expiryDuration = duration; expiryUnit = unit; return this; } /** * Set this loading provider so that entries never expire * * @since 0.5 * * @return this */ public Builder neverExpires() { expiryDuration = 0L; return this; } /** * Build the provider * * @return a {@link LoadingMessageSourceProvider} * @throws IllegalArgumentException no loader has been provided */ public MessageSourceProvider build() { BUNDLE.checkArgument(loader != null, "cfg.noLoader"); return new LoadingMessageSourceProvider(this); } } }