/**
* Copyright (c) 2012-2017, jcabi.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met: 1) Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer. 2) Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution. 3) Neither the name of the jcabi.com nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcabi.aspects.aj;
import com.jcabi.aspects.Loggable;
import com.jcabi.log.VerboseRunnable;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* For updating cache and cleaning expired cache.
* @author Peng Chuanjiang (pengchuanjiang@souyidai.com)
* @version $Id: 4c18b638735db6e83ba1da68a4d685c6e12aa222 $
*/
@SuppressWarnings("PMD.DoNotUseThreads")
public final class UpdateMethodCacher {
/**
* Calling tunnels.
*/
private final transient
ConcurrentMap<MethodCacher.Key, MethodCacher.Tunnel> tunnels;
/**
* Service that cleans cache.
*/
private final transient ScheduledExecutorService cleaner;
/**
* Save the keys of caches which need update.
*/
private final transient BlockingQueue<MethodCacher.Key> updatekeys;
/**
* Service that update cache.
*/
private final transient ScheduledExecutorService updater;
/**
* Public ctor.
* @param tls ConcurrentMap
* @param ukeys BlockingQueue
*/
public UpdateMethodCacher(
final ConcurrentMap<MethodCacher.Key, MethodCacher.Tunnel> tls,
final BlockingQueue<MethodCacher.Key> ukeys) {
this.tunnels = tls;
this.updatekeys = ukeys;
this.cleaner = Executors.newSingleThreadScheduledExecutor(
new NamedThreads(
"cacheable-clean",
"automated cleaning of expired @Cacheable values"
)
);
this.updater = Executors.newSingleThreadScheduledExecutor(
new NamedThreads(
"cacheable-update",
"async update of expired @Cacheable values"
)
);
}
/**
* Staring two threads(cheaner and updater) are used for
* updating cache and cleaning expired cache.
*/
public void start() {
this.cleaner.scheduleWithFixedDelay(
new VerboseRunnable(
new Runnable() {
@Override
public void run() {
UpdateMethodCacher.this.clean();
}
}
),
1L, 1L, TimeUnit.SECONDS
);
this.updater.schedule(
new VerboseRunnable(
new Runnable() {
@Override
public void run() {
UpdateMethodCacher.this.update();
}
}
),
0L, TimeUnit.SECONDS
);
}
/**
* Clean cache.
*/
private void clean() {
synchronized (this.tunnels) {
for (final MethodCacher.Key key : this.tunnels.keySet()) {
if (this.tunnels.get(key).expired()
&& !this.tunnels.get(key).asyncUpdate()) {
final MethodCacher.Tunnel tunnel = this.tunnels.remove(key);
LogHelper.log(
key.getLevel(),
this,
"%s:%s expired in cache",
key,
tunnel
);
}
}
}
}
/**
* Update cache.
*/
@SuppressWarnings("PMD.AvoidCatchingThrowable")
private void update() {
while (true) {
try {
final MethodCacher.Key key = this.updatekeys.take();
final MethodCacher.Tunnel tunnel = this.tunnels.get(key);
if (tunnel != null && tunnel.expired()) {
final MethodCacher.Tunnel newTunnel = tunnel.copy();
newTunnel.through();
this.tunnels.put(key, newTunnel);
}
} catch (final InterruptedException ex) {
LogHelper.log(
Loggable.ERROR,
this,
"%s:%s",
ex.getMessage(),
ex
);
// @checkstyle IllegalCatch (1 line)
} catch (final Throwable ex) {
LogHelper.log(
Loggable.ERROR,
this,
"Exception message is %s, Exception is %s",
ex.getMessage(),
ex
);
}
}
}
}