Add option cache feature

pull/146/head
johnniang 2019-04-29 02:06:02 +08:00
parent 6e8ef4b35a
commit cea6a55963
7 changed files with 126 additions and 63 deletions

View File

@ -38,21 +38,17 @@ public class FreemarkerConfigAwareListener {
private final ThemeSettingService themeSettingService; private final ThemeSettingService themeSettingService;
private final OptionService optionsService;
private final UserService userService; private final UserService userService;
public FreemarkerConfigAwareListener(OptionService optionService, public FreemarkerConfigAwareListener(OptionService optionService,
Configuration configuration, Configuration configuration,
ThemeService themeService, ThemeService themeService,
ThemeSettingService themeSettingService, ThemeSettingService themeSettingService,
OptionService optionsService,
UserService userService) { UserService userService) {
this.optionService = optionService; this.optionService = optionService;
this.configuration = configuration; this.configuration = configuration;
this.themeService = themeService; this.themeService = themeService;
this.themeSettingService = themeSettingService; this.themeSettingService = themeSettingService;
this.optionsService = optionsService;
this.userService = userService; this.userService = userService;
} }
@ -97,13 +93,11 @@ public class FreemarkerConfigAwareListener {
} }
private void loadOptionsConfig() throws TemplateModelException { private void loadOptionsConfig() throws TemplateModelException {
Map<String, Object> options = optionService.listOptions(); configuration.setSharedVariable("options", optionService.listOptions());
configuration.setSharedVariable("options", options);
} }
private void loadThemeConfig() throws TemplateModelException { private void loadThemeConfig() throws TemplateModelException {
ThemeProperty activatedTheme = themeService.getActivatedTheme(); configuration.setSharedVariable("theme", themeService.getActivatedTheme());
configuration.setSharedVariable("theme", activatedTheme);
configuration.setSharedVariable("settings", themeSettingService.listAsMapBy(themeService.getActivatedThemeId())); configuration.setSharedVariable("settings", themeSettingService.listAsMapBy(themeService.getActivatedThemeId()));
} }
} }

View File

@ -0,0 +1,29 @@
package run.halo.app.event.options;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.service.OptionService;
/**
* Option updated listener.
*
* @author johnniang
* @date 19-4-29
*/
@Component
public class OptionUpdatedListener implements ApplicationListener<OptionUpdatedEvent> {
private final StringCacheStore cacheStore;
public OptionUpdatedListener(StringCacheStore cacheStore) {
this.cacheStore = cacheStore;
}
@Override
@Async
public void onApplicationEvent(OptionUpdatedEvent event) {
cacheStore.delete(OptionService.OPTIONS_KEY);
}
}

View File

@ -0,0 +1,21 @@
package run.halo.app.event.theme;
import org.springframework.context.ApplicationEvent;
/**
* Theme updated event.
*
* @author johnniang
* @date 19-4-29
*/
public class ThemeUpdatedEvent extends ApplicationEvent {
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public ThemeUpdatedEvent(Object source) {
super(source);
}
}

View File

@ -0,0 +1,29 @@
package run.halo.app.event.theme;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.service.ThemeService;
/**
* Theme updated listener.
*
* @author johnniang
* @date 19-4-29
*/
@Component
public class ThemeUpdatedListener implements ApplicationListener<ThemeUpdatedEvent> {
private final StringCacheStore cacheStore;
public ThemeUpdatedListener(StringCacheStore cacheStore) {
this.cacheStore = cacheStore;
}
@Override
@Async
public void onApplicationEvent(ThemeUpdatedEvent event) {
cacheStore.delete(ThemeService.THEMES_CACHE_KEY);
}
}

View File

@ -30,14 +30,7 @@ public interface OptionService extends CrudService<Option, Integer> {
int DEFAULT_RSS_PAGE_SIZE = 20; int DEFAULT_RSS_PAGE_SIZE = 20;
/** String OPTIONS_KEY = "options";
* Save one option
*
* @param key key must not be blank
* @param value value
*/
@Transactional
void save(@NonNull String key, String value);
/** /**
* Save multiple options * Save multiple options

View File

@ -19,7 +19,6 @@ import run.halo.app.model.params.OptionParam;
import run.halo.app.model.properties.*; import run.halo.app.model.properties.*;
import run.halo.app.repository.OptionRepository; import run.halo.app.repository.OptionRepository;
import run.halo.app.service.OptionService; import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService;
import run.halo.app.service.base.AbstractCrudService; import run.halo.app.service.base.AbstractCrudService;
import run.halo.app.utils.HaloUtils; import run.halo.app.utils.HaloUtils;
import run.halo.app.utils.ServiceUtils; import run.halo.app.utils.ServiceUtils;
@ -60,8 +59,7 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
propertyEnumMap = Collections.unmodifiableMap(PropertyEnum.getValuePropertyEnumMap()); propertyEnumMap = Collections.unmodifiableMap(PropertyEnum.getValuePropertyEnumMap());
} }
@Override private void save(String key, String value) {
public void save(String key, String value) {
Assert.hasText(key, "Option key must not be blank"); Assert.hasText(key, "Option key must not be blank");
if (StringUtils.isBlank(value)) { if (StringUtils.isBlank(value)) {
@ -90,8 +88,6 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
Option savedOption = optionRepository.save(option); Option savedOption = optionRepository.save(option);
log.debug("Saved option: [{}]", savedOption); log.debug("Saved option: [{}]", savedOption);
cacheStore.delete(ThemeService.THEMES_CACHE_KEY);
} }
@Override @Override
@ -103,7 +99,7 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
// TODO Optimize the queries // TODO Optimize the queries
options.forEach(this::save); options.forEach(this::save);
publishEvent(); publishOptionUpdatedEvent();
} }
@Override @Override
@ -115,7 +111,7 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
// TODO Optimize the query // TODO Optimize the query
optionParams.forEach(optionParam -> save(optionParam.getKey(), optionParam.getValue())); optionParams.forEach(optionParam -> save(optionParam.getKey(), optionParam.getValue()));
publishEvent(); publishOptionUpdatedEvent();
} }
@Override @Override
@ -124,7 +120,7 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
save(property.getValue(), value); save(property.getValue(), value);
publishEvent(); publishOptionUpdatedEvent();
} }
@Override @Override
@ -135,44 +131,51 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
properties.forEach((property, value) -> save(property.getValue(), value)); properties.forEach((property, value) -> save(property.getValue(), value));
publishEvent(); publishOptionUpdatedEvent();
} }
@Override @Override
@SuppressWarnings("unchecked")
public Map<String, Object> listOptions() { public Map<String, Object> listOptions() {
List<Option> options = listAll(); // Get options from cache
return cacheStore.getAny(OPTIONS_KEY, Map.class).orElseGet(() -> {
List<Option> options = listAll();
Set<String> keys = ServiceUtils.fetchProperty(options, Option::getKey); Set<String> keys = ServiceUtils.fetchProperty(options, Option::getKey);
Map<String, Object> userDefinedOptionMap = ServiceUtils.convertToMap(options, Option::getKey, option -> { Map<String, Object> userDefinedOptionMap = ServiceUtils.convertToMap(options, Option::getKey, option -> {
String key = option.getKey(); String key = option.getKey();
PropertyEnum propertyEnum = propertyEnumMap.get(key); PropertyEnum propertyEnum = propertyEnumMap.get(key);
if (propertyEnum == null) { if (propertyEnum == null) {
return option.getValue(); return option.getValue();
} }
return PropertyEnum.convertTo(option.getValue(), propertyEnum); return PropertyEnum.convertTo(option.getValue(), propertyEnum);
});
Map<String, Object> result = new HashMap<>(userDefinedOptionMap);
// Add default property
propertyEnumMap.keySet()
.stream()
.filter(key -> !keys.contains(key))
.forEach(key -> {
PropertyEnum propertyEnum = propertyEnumMap.get(key);
if (StringUtils.isBlank(propertyEnum.defaultValue())) {
return;
}
result.put(key, PropertyEnum.convertTo(propertyEnum.defaultValue(), propertyEnum));
});
// Cache the result
cacheStore.putAny(OPTIONS_KEY, result);
return result;
}); });
Map<String, Object> result = new HashMap<>(userDefinedOptionMap);
// Add default property
propertyEnumMap.keySet()
.stream()
.filter(key -> !keys.contains(key))
.forEach(key -> {
PropertyEnum propertyEnum = propertyEnumMap.get(key);
if (StringUtils.isBlank(propertyEnum.defaultValue())) {
return;
}
result.put(key, PropertyEnum.convertTo(propertyEnum.defaultValue(), propertyEnum));
});
return result;
} }
@Override @Override
@ -357,7 +360,7 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
return blogUrl; return blogUrl;
} }
private void publishEvent() { private void publishOptionUpdatedEvent() {
eventPublisher.publishEvent(new OptionUpdatedEvent(this)); eventPublisher.publishEvent(new OptionUpdatedEvent(this));
} }
} }

View File

@ -22,6 +22,7 @@ import org.springframework.web.multipart.MultipartFile;
import run.halo.app.cache.StringCacheStore; import run.halo.app.cache.StringCacheStore;
import run.halo.app.config.properties.HaloProperties; import run.halo.app.config.properties.HaloProperties;
import run.halo.app.event.theme.ThemeActivatedEvent; import run.halo.app.event.theme.ThemeActivatedEvent;
import run.halo.app.event.theme.ThemeUpdatedEvent;
import run.halo.app.exception.*; import run.halo.app.exception.*;
import run.halo.app.handler.theme.config.ThemeConfigResolver; import run.halo.app.handler.theme.config.ThemeConfigResolver;
import run.halo.app.handler.theme.config.ThemePropertyResolver; import run.halo.app.handler.theme.config.ThemePropertyResolver;
@ -237,7 +238,7 @@ public class ThemeServiceImpl implements ThemeService {
FileUtil.del(Paths.get(themeProperty.getThemePath())); FileUtil.del(Paths.get(themeProperty.getThemePath()));
// Delete theme cache // Delete theme cache
clearThemeCache(); eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
} catch (Exception e) { } catch (Exception e) {
throw new ServiceException("Failed to delete theme folder", e).setErrorData(themeId); throw new ServiceException("Failed to delete theme folder", e).setErrorData(themeId);
} }
@ -329,7 +330,7 @@ public class ThemeServiceImpl implements ThemeService {
setActivatedTheme(themeProperty); setActivatedTheme(themeProperty);
// Clear the cache // Clear the cache
clearThemeCache(); eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
// Publish a theme activated event // Publish a theme activated event
eventPublisher.publishEvent(new ThemeActivatedEvent(this)); eventPublisher.publishEvent(new ThemeActivatedEvent(this));
@ -402,7 +403,7 @@ public class ThemeServiceImpl implements ThemeService {
ThemeProperty property = getProperty(targetThemePath); ThemeProperty property = getProperty(targetThemePath);
// Clear theme cache // Clear theme cache
clearThemeCache(); this.eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
// Delete cache // Delete cache
return property; return property;
@ -496,13 +497,6 @@ public class ThemeServiceImpl implements ThemeService {
return Files.createTempDirectory("halo"); return Files.createTempDirectory("halo");
} }
/**
* Clears theme cache.
*/
private void clearThemeCache() {
cacheStore.delete(THEMES_CACHE_KEY);
}
/** /**
* Sets activated theme. * Sets activated theme.
* *