com.github.fge.msgsimple.provider.LoadingMessageSourceProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of msg-simple Show documentation
Show all versions of msg-simple Show documentation
Uploads all artifacts belonging to configuration ':archives'
/*
* Copyright (c) 2013, Francis Galiegue
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
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.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* A caching, on-demand loading message source provider
*
* 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.
*
* Important notes:
*
*
* - the default source is also returned if the load fails with an
* exception;
* - when a source is loaded, it is permanently cached;
* - there is also a timeout for loading (which is 5 seconds by default);
* if the timeout is reached, the loading is attempted again the next time
* the locale is asked for.
*
*
* You cannot instantiate that class directly; use {@link #newBuilder()} to
* obtain a builder class and set up your provider.
*/
@ThreadSafe
public final class LoadingMessageSourceProvider
implements MessageSourceProvider
{
private static final InternalBundle BUNDLE
= InternalBundle.getInstance();
private static final int NTHREADS = 5;
private final MessageSourceLoader loader;
private final MessageSource defaultSource;
private final long nr;
private final TimeUnit unit;
private final ExecutorService service
= Executors.newFixedThreadPool(NTHREADS);
private final Map> sources
= new HashMap>();
private LoadingMessageSourceProvider(final Builder builder)
{
loader = builder.loader;
defaultSource = builder.defaultSource;
nr = builder.nr;
unit = builder.unit;
}
/**
* Create a new builder
*
* @return an empty builder
*/
public static Builder newBuilder()
{
return new Builder();
}
@Override
public MessageSource getMessageSource(final Locale locale)
{
FutureTask task;
/*
* The algorithm is as follows:
*
* - access the sources map in a synchronous manner;
* - grab the FutureTask matching the required locale:
* - if no task exists, create it;
* - if it exists but has been cancelled (in the event of a timeout,
* see below), create it anew;
* - always within the synchronized access to sources, submit the task
* for immediate execution to our ExecutorService;
* - to be followed...
*/
synchronized (sources) {
task = sources.get(locale);
if (task == null || task.isCancelled()) {
task = loadingTask(locale);
sources.put(locale, task);
service.execute(task);
}
}
/*
* - try and get the result of the task, with a timeout;
* - if we get a result in time, return it, or the default source if
* the result is null;
* - in the event of an error, return the default source; in addition,
* if this is a timeout, cancel the task.
*/
try {
final MessageSource source = task.get(nr, unit);
return source == null ? defaultSource : source;
} catch (InterruptedException ignored) {
return defaultSource;
} catch (ExecutionException ignored) {
return defaultSource;
} catch (TimeoutException ignored) {
task.cancel(true);
return defaultSource;
}
}
private FutureTask loadingTask(final Locale locale)
{
return new FutureTask(new Callable()
{
@Override
public MessageSource call()
throws IOException
{
return loader.load(locale);
}
});
}
/**
* Builder class for a {@link LoadingMessageSourceProvider}
*/
public static final class Builder
{
private MessageSourceLoader loader;
private MessageSource defaultSource;
private long nr = 5L;
private TimeUnit unit = TimeUnit.SECONDS;
private Builder()
{
}
/**
* Set the message source loader
*
* @param loader the loader
* @throws NullPointerException loader is null
* @return this
*/
public Builder setLoader(final MessageSourceLoader loader)
{
BUNDLE.checkNotNull(loader, "cfg.nullLoader");
this.loader = loader;
return this;
}
/**
* Set the default message source if the loader fails to load
*
* @param defaultSource the default source
* @throws NullPointerException source is null
* @return this
*/
public Builder setDefaultSource(final MessageSource defaultSource)
{
BUNDLE.checkNotNull(defaultSource, "cfg.nullDefaultSource");
this.defaultSource = defaultSource;
return this;
}
/**
* Set the load timeout
*
* @param nr number of units
* @param unit the time unit
* @throws IllegalArgumentException {@code nr} is negative or zero
* @throws NullPointerException time unit is null
* @return this
*/
public Builder setTimeout(final long nr, final TimeUnit unit)
{
BUNDLE.checkArgument(nr > 0L, "cfg.nonPositiveTimeout");
BUNDLE.checkNotNull(unit, "cfg.nullTimeUnit");
this.nr = nr;
this.unit = unit;
return this;
}
/**
* Build the provider
*
* @return a {@link LoadingMessageSourceProvider}
*/
public MessageSourceProvider build()
{
BUNDLE.checkArgument(loader != null, "cfg.noLoader");
return new LoadingMessageSourceProvider(this);
}
}
}