Line data Source code
1 : // Copyright (C) 2021 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.mail.send; 16 : 17 : import static com.google.common.base.Preconditions.checkArgument; 18 : 19 : import com.google.common.cache.CacheLoader; 20 : import com.google.common.cache.LoadingCache; 21 : import com.google.common.util.concurrent.ListenableFuture; 22 : import com.google.common.util.concurrent.ListeningExecutorService; 23 : import com.google.gerrit.server.CacheRefreshExecutor; 24 : import com.google.gerrit.server.cache.CacheModule; 25 : import com.google.inject.Inject; 26 : import com.google.inject.ProvisionException; 27 : import com.google.inject.Singleton; 28 : import com.google.inject.name.Named; 29 : import com.google.template.soy.jbcsrc.api.SoySauce; 30 : import java.time.Duration; 31 : import java.util.concurrent.ExecutionException; 32 : import javax.inject.Provider; 33 : 34 : /** 35 : * Provides support for soy templates 36 : * 37 : * <p>Module loads templates with {@link MailSoySauceLoader} and caches compiled templates. The 38 : * cache refreshes automatically, so Gerrit does not need to be restarted if templates are changed. 39 : */ 40 152 : public class MailSoySauceModule extends CacheModule { 41 : static final String CACHE_NAME = "soy_sauce_compiled_templates"; 42 : private static final String SOY_LOADING_CACHE_KEY = "KEY"; 43 : 44 : @Override 45 : protected void configure() { 46 : // Cache stores only a single key-value pair (key is SOY_LOADING_CACHE_KEY). We are using 47 : // cache only for it refresh/expire logic. 48 152 : cache(CACHE_NAME, String.class, SoySauce.class) 49 : // Cache refreshes a value only on the access (if refreshAfterWrite interval is 50 : // passed). While the value is refreshed, cache returns old value. 51 : // Adding expireAfterWrite interval prevents cache from returning very old template. 52 152 : .refreshAfterWrite(Duration.ofSeconds(5)) 53 152 : .expireAfterWrite(Duration.ofMinutes(1)) 54 152 : .loader(SoySauceCacheLoader.class); 55 152 : bind(SoySauce.class).annotatedWith(MailTemplates.class).toProvider(SoySauceProvider.class); 56 152 : } 57 : 58 : @Singleton 59 : static class SoySauceProvider implements Provider<SoySauce> { 60 : private final LoadingCache<String, SoySauce> templateCache; 61 : 62 : @Inject 63 146 : SoySauceProvider(@Named(CACHE_NAME) LoadingCache<String, SoySauce> templateCache) { 64 146 : this.templateCache = templateCache; 65 146 : } 66 : 67 : @Override 68 : public SoySauce get() { 69 : try { 70 106 : return templateCache.get(SOY_LOADING_CACHE_KEY); 71 0 : } catch (ExecutionException e) { 72 0 : throw new ProvisionException("Can't get SoySauce from the cache", e); 73 : } 74 : } 75 : } 76 : 77 : @Singleton 78 : static class SoySauceCacheLoader extends CacheLoader<String, SoySauce> { 79 : private final ListeningExecutorService executor; 80 : private final MailSoySauceLoader loader; 81 : 82 : @Inject 83 : SoySauceCacheLoader( 84 152 : @CacheRefreshExecutor ListeningExecutorService executor, MailSoySauceLoader loader) { 85 152 : this.executor = executor; 86 152 : this.loader = loader; 87 152 : } 88 : 89 : @Override 90 : public SoySauce load(String key) throws Exception { 91 106 : checkArgument( 92 106 : SOY_LOADING_CACHE_KEY.equals(key), 93 : "Cache can have only one element with a key '%s'", 94 : SOY_LOADING_CACHE_KEY); 95 106 : return loader.load(); 96 : } 97 : 98 : @Override 99 : public ListenableFuture<SoySauce> reload(String key, SoySauce soySauce) { 100 41 : return executor.submit(() -> loader.load()); 101 : } 102 : } 103 : }