001/** 002 * Copyright (C) 2006-2025 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; 019import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; 020 021import java.lang.annotation.Annotation; 022import java.util.Collections; 023import java.util.Map; 024import java.util.concurrent.TimeUnit; 025import java.util.stream.StreamSupport; 026 027import javax.annotation.PostConstruct; 028import javax.annotation.PreDestroy; 029import javax.cache.Cache; 030import javax.cache.CacheManager; 031import javax.cache.annotation.CacheMethodDetails; 032import javax.cache.annotation.CacheResolver; 033import javax.cache.annotation.CacheResolverFactory; 034import javax.cache.annotation.CacheResult; 035import javax.cache.configuration.Configuration; 036import javax.enterprise.context.ApplicationScoped; 037import javax.inject.Inject; 038import javax.ws.rs.core.Response; 039 040import org.apache.geronimo.jcache.simple.cdi.CacheResolverImpl; 041import org.eclipse.microprofile.config.inject.ConfigProperty; 042import org.talend.sdk.component.api.meta.Documentation; 043import org.talend.sdk.component.server.api.CacheResource; 044import org.talend.sdk.component.server.front.ComponentResourceImpl; 045import org.talend.sdk.component.server.front.ConfigurationTypeResourceImpl; 046import org.talend.sdk.component.server.front.EnvironmentResourceImpl; 047import org.talend.sdk.component.server.front.model.Environment; 048import org.talend.sdk.component.server.service.ComponentManagerService; 049import org.talend.sdk.components.vault.jcache.CacheConfigurationFactory; 050import org.talend.sdk.components.vault.jcache.CacheSizeManager; 051 052import lombok.extern.slf4j.Slf4j; 053 054@Slf4j 055@ApplicationScoped 056public class FrontCacheResolver implements CacheResolverFactory, CacheResource { 057 058 @Inject 059 private CacheManager cacheManager; 060 061 @Inject 062 private CacheConfigurationFactory cacheConfiguration; 063 064 @Inject 065 @Documentation("How often (in ms) should we invalidate the credentials caches.") 066 @ConfigProperty(name = "talend.vault.cache.jcache.refresh.period", defaultValue = "30000") 067 private Long refreshPeriod; 068 069 @Inject 070 private ComponentManagerService service; 071 072 @Inject 073 EnvironmentResourceImpl env; 074 075 @Inject 076 ComponentResourceImpl components; 077 078 @Inject 079 ConfigurationTypeResourceImpl resources; 080 081 private long lastUpdated; 082 083 private volatile boolean running = true; 084 085 private Thread thread; 086 087 @PostConstruct 088 private void startRefresh() { 089 lastUpdated = System.currentTimeMillis(); 090 thread = new Thread(() -> refreshThread(refreshPeriod)); 091 thread.setName(getClass().getName() + "-refresher"); 092 thread.setPriority(Thread.NORM_PRIORITY); 093 thread.setDaemon(false); 094 thread.setUncaughtExceptionHandler((t, e) -> log.error(e.getMessage(), e)); 095 thread.start(); 096 } 097 098 @PreDestroy 099 private void stopRefresh() { 100 running = false; 101 ofNullable(thread).ifPresent(it -> { 102 try { 103 it.interrupt(); 104 it.join(TimeUnit.SECONDS.toMillis(5)); // not super important here 105 } catch (final InterruptedException e) { 106 log.warn(e.getMessage()); 107 Thread.currentThread().interrupt(); 108 } 109 }); 110 } 111 112 private void refreshThread(final long delay) { 113 try { 114 while (running) { 115 try { 116 updateIfNeeded(); 117 } catch (final Exception e) { 118 log.warn(e.getMessage(), e); 119 } 120 Thread.sleep(delay); 121 } 122 } catch (final InterruptedException ie) { 123 Thread.currentThread().interrupt(); 124 } 125 } 126 127 private void updateIfNeeded() { 128 final Environment environment = env.get(); 129 // assumes time are synch-ed but not a high assumption 130 if (lastUpdated < environment.getLastUpdated().getTime()) { 131 cleanupCaches(); 132 lastUpdated = System.currentTimeMillis(); 133 } 134 } 135 136 /** 137 * Clear all soft caches 138 */ 139 public void cleanupCaches() { 140 StreamSupport 141 .stream(cacheManager.getCacheNames().spliterator(), false) 142 .filter(name -> name.startsWith("org.talend.sdk.component.server.front.")) 143 .peek(c -> log.info("[clearCaches] clear cache {}.", c)) 144 .forEach(r -> cacheManager.getCache(r).clear()); 145 components.clearCache(null); 146 resources.clearCache(null); 147 } 148 149 @Override 150 public Response clearCaches() { 151 final long clearedCacheCount = countActiveCaches(); 152 Map<String, Long> stringStringMap = Collections.singletonMap("clearedCacheCount", clearedCacheCount); 153 service.redeployPlugins(); 154 return Response 155 .ok(stringStringMap) 156 .type(APPLICATION_JSON_TYPE) 157 .build(); 158 } 159 160 /** 161 * mainly used for testing purpose. 162 * 163 * @return active caches count 164 */ 165 public Long countActiveCaches() { 166 return StreamSupport 167 .stream(cacheManager.getCacheNames().spliterator(), false) 168 .filter(name -> name.startsWith("org.talend.sdk.component.server.front.")) 169 .filter(c -> cacheManager.getCache(c).iterator().hasNext()) 170 .count(); 171 } 172 173 @Override 174 public CacheResolver getCacheResolver(final CacheMethodDetails<? extends Annotation> cacheMethodDetails) { 175 return findCacheResolver(cacheMethodDetails.getCacheName()); 176 } 177 178 @Override 179 public CacheResolver getExceptionCacheResolver(final CacheMethodDetails<CacheResult> cacheMethodDetails) { 180 return findCacheResolver(cacheMethodDetails.getCacheAnnotation().exceptionCacheName()); 181 } 182 183 private CacheResolver findCacheResolver(final String exceptionCacheName) { 184 Cache<?, ?> cache = cacheManager.getCache(exceptionCacheName); 185 if (cache == null) { 186 try { 187 synchronized (this) { 188 cache = createCache(exceptionCacheName); 189 } 190 } catch (final Exception ce) { 191 log.warn("[findCacheResolver] createCache failed: {}.", ce.getMessage()); 192 cache = cacheManager.getCache(exceptionCacheName); 193 } 194 } 195 return new CacheResolverImpl(cache); 196 } 197 198 private Cache<?, ?> createCache(final String exceptionCacheName) { 199 log.debug("[createCache] {}", exceptionCacheName); 200 final CacheSizeManager<Object, Object> listener = new CacheSizeManager<>(cacheConfiguration.maxSize()); 201 final Configuration<Object, Object> configuration = cacheConfiguration.createConfiguration(listener); 202 final Cache<Object, Object> instance = cacheManager.createCache(exceptionCacheName, configuration); 203 listener.accept(instance); 204 return instance; 205 } 206}