001/**
002 * Copyright (C) 2006-2022 Talend Inc. - www.talend.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.talend.sdk.component.server.service.jcache;
017
018import static java.util.Optional.ofNullable;
019
020import java.lang.annotation.Annotation;
021import java.util.concurrent.TimeUnit;
022import java.util.stream.StreamSupport;
023
024import javax.annotation.PostConstruct;
025import javax.annotation.PreDestroy;
026import javax.cache.Cache;
027import javax.cache.CacheException;
028import javax.cache.CacheManager;
029import javax.cache.annotation.CacheMethodDetails;
030import javax.cache.annotation.CacheResolver;
031import javax.cache.annotation.CacheResolverFactory;
032import javax.cache.annotation.CacheResult;
033import javax.cache.configuration.Configuration;
034import javax.enterprise.context.ApplicationScoped;
035import javax.inject.Inject;
036
037import org.apache.geronimo.jcache.simple.cdi.CacheResolverImpl;
038import org.eclipse.microprofile.config.inject.ConfigProperty;
039import org.talend.sdk.component.api.meta.Documentation;
040import org.talend.sdk.component.server.front.EnvironmentResourceImpl;
041import org.talend.sdk.component.server.front.model.Environment;
042import org.talend.sdk.components.vault.jcache.CacheConfigurationFactory;
043import org.talend.sdk.components.vault.jcache.CacheSizeManager;
044
045import lombok.extern.slf4j.Slf4j;
046
047@Slf4j
048@ApplicationScoped
049public class FrontCacheResolver implements CacheResolverFactory {
050
051    @Inject
052    private CacheManager cacheManager;
053
054    @Inject
055    private CacheConfigurationFactory cacheConfiguration;
056
057    @Inject
058    @Documentation("How often (in ms) should we invalidate the credentials caches.")
059    @ConfigProperty(name = "talend.vault.cache.jcache.refresh.period", defaultValue = "30000")
060    private Long refreshPeriod;
061
062    @Inject
063    EnvironmentResourceImpl env;
064
065    private long lastUpdated;
066
067    private volatile boolean running = true;
068
069    private Thread thread;
070
071    @PostConstruct
072    private void startRefresh() {
073        lastUpdated = System.currentTimeMillis();
074        thread = new Thread(() -> refreshThread(refreshPeriod));
075        thread.setName(getClass().getName() + "-refresher");
076        thread.setPriority(Thread.NORM_PRIORITY);
077        thread.setDaemon(false);
078        thread.setUncaughtExceptionHandler((t, e) -> log.error(e.getMessage(), e));
079        thread.start();
080    }
081
082    @PreDestroy
083    private void stopRefresh() {
084        running = false;
085        ofNullable(thread).ifPresent(it -> {
086            try {
087                it.interrupt();
088                it.join(TimeUnit.SECONDS.toMillis(5)); // not super important here
089            } catch (final InterruptedException e) {
090                log.warn(e.getMessage());
091                Thread.currentThread().interrupt();
092            }
093        });
094    }
095
096    private void refreshThread(final long delay) {
097        try {
098            while (running) {
099                try {
100                    updateIfNeeded();
101                } catch (final Exception e) {
102                    log.warn(e.getMessage(), e);
103                }
104                Thread.sleep(delay);
105            }
106        } catch (final InterruptedException ie) {
107            Thread.currentThread().interrupt();
108        }
109    }
110
111    private void updateIfNeeded() {
112        final Environment environment = env.get();
113        // assumes time are synch-ed but not a high assumption
114        if (lastUpdated < environment.getLastUpdated().getTime()) {
115            clearCaches();
116            lastUpdated = System.currentTimeMillis();
117        }
118    }
119
120    private void clearCaches() {
121        StreamSupport
122                .stream(cacheManager.getCacheNames().spliterator(), false)
123                .filter(name -> name.startsWith("org.talend.sdk.component.server.front."))
124                .peek(c -> log.warn("[clearCaches] clear cache {}."))
125                .forEach(r -> cacheManager.getCache(r).clear());
126    }
127
128    @Override
129    public CacheResolver getCacheResolver(final CacheMethodDetails<? extends Annotation> cacheMethodDetails) {
130        return findCacheResolver(cacheMethodDetails.getCacheName());
131    }
132
133    @Override
134    public CacheResolver getExceptionCacheResolver(final CacheMethodDetails<CacheResult> cacheMethodDetails) {
135        return findCacheResolver(cacheMethodDetails.getCacheAnnotation().exceptionCacheName());
136    }
137
138    private CacheResolver findCacheResolver(final String exceptionCacheName) {
139        Cache<?, ?> cache = cacheManager.getCache(exceptionCacheName);
140        if (cache == null) {
141            try {
142                cache = createCache(exceptionCacheName);
143            } catch (final CacheException ce) {
144                cache = cacheManager.getCache(exceptionCacheName);
145            }
146        }
147        return new CacheResolverImpl(cache);
148    }
149
150    private Cache<?, ?> createCache(final String exceptionCacheName) {
151        log.debug("[createCache] {}", exceptionCacheName);
152        final CacheSizeManager<Object, Object> listener = new CacheSizeManager<>(cacheConfiguration.maxSize());
153        final Configuration<Object, Object> configuration = cacheConfiguration.createConfiguration(listener);
154        final Cache<Object, Object> instance = cacheManager.createCache(exceptionCacheName, configuration);
155        listener.accept(instance);
156        return instance;
157    }
158}