@@ -29,6 +29,9 @@ class StatisticsFactory {
29
29
@Autowired
30
30
GrailsApplication grailsApplication
31
31
32
+ /* * Lazily initialized in the getConfig method based on the HOME_PAGE_STATISTICS setting */
33
+ private Map config
34
+
32
35
public StatisticsFactory () {}
33
36
34
37
private String initialize () {
@@ -46,29 +49,52 @@ class StatisticsFactory {
46
49
public synchronized void clearConfig () {
47
50
Cache cache = grailsCacheManager. getCache(STATISTICS_CACHE_REGION )
48
51
cache. clear()
52
+ config = null
49
53
}
50
54
51
55
private synchronized Map getConfig () {
52
- String config = grailsCacheManager. getCache(STATISTICS_CACHE_REGION ). get(CONFIG_KEY )?. get()
53
- if (! config) {
54
- config = initialize()
55
- grailsCacheManager. getCache(STATISTICS_CACHE_REGION ). put(CONFIG_KEY , config)
56
+ String configString = grailsCacheManager. getCache(STATISTICS_CACHE_REGION ). get(CONFIG_KEY )?. get()
57
+ // We check for the config string cache instead of whether this.config is null because
58
+ // this allows the generic admin cache page to clear the cache and allow a config update
59
+ // without having to know to call StatisticsFactory.clearConfig()
60
+ // We can't cache the parsed config directly as it is not serializable.
61
+ if (! configString || ! this . config) { // Check both as the cache can survive a restart sometimes, but not the field.
62
+ configString = initialize()
63
+ grailsCacheManager. getCache(STATISTICS_CACHE_REGION ). put(CONFIG_KEY , configString)
64
+ JsonSlurper jsonSlurper = new JsonSlurper ()
65
+ this . config = Collections . synchronizedMap(jsonSlurper. parseText(configString))
56
66
}
57
- JsonSlurper jsonSlurper = new JsonSlurper ()
58
- jsonSlurper . parseText( config)
67
+
68
+ this . config
59
69
}
60
70
61
- public synchronized List<Map > getStatisticsGroup (int groupNumber ) {
71
+ public List<Map > getStatisticsGroupFromCache (int groupNumber ) {
72
+ fromCache(groupNumber)
73
+ }
74
+
75
+ public List<Map > getStatisticsGroup (int groupNumber ) {
62
76
63
77
Map config = getConfig()
64
- List<Map > statistics = fromCache(groupNumber)
65
- if (! statistics) {
66
- log. info(" Cache miss for homepage stats, key: ${ groupNumber} " )
67
- statistics = this . config. groups[groupNumber]. collect { statisticName ->
68
- Map statistic = config. statistics[statisticName]
69
- evaluateStatistic(statistic)
78
+ List groupConfig = config. groups[groupNumber]
79
+ if (! groupConfig) {
80
+ log. error(" No configuration found for group number: ${ groupNumber} " )
81
+ return null
82
+ }
83
+
84
+ List<Map > statistics
85
+ // Only one thread should recalculate the statistics. The config is a shared
86
+ // synchronized instance so is safe to synchronize on.
87
+ synchronized (groupConfig) {
88
+ statistics = fromCache(groupNumber)
89
+ if (! statistics) {
90
+ log. info(" Cache miss for homepage stats, key: ${ groupNumber} - recalculating..." )
91
+ statistics = groupConfig. collect { statisticName ->
92
+ Map statistic = config. statistics[statisticName]
93
+ evaluateStatistic(statistic)
94
+ }
95
+ log. info(" Caching homepage stats, key: ${ groupNumber} " )
96
+ cache(groupNumber, statistics)
70
97
}
71
- cache(groupNumber, statistics)
72
98
}
73
99
statistics
74
100
}
@@ -87,13 +113,19 @@ class StatisticsFactory {
87
113
stats
88
114
}
89
115
90
- public Map randomGroup (int exclude = -1 ) {
116
+ public Map randomGroup (int exclude = -1 , boolean recacluateIfMissing = false ) {
91
117
int groupCount = getGroupCount()
92
118
int group = Math . floor(Math . random()* groupCount)
93
119
while (group == exclude) {
94
120
group = Math . floor(Math . random()* groupCount)
95
121
}
96
- List stats = getStatisticsGroup(group)
122
+ List stats
123
+ if (recacluateIfMissing) {
124
+ stats = getStatisticsGroup(group)
125
+ }
126
+ else {
127
+ stats = getStatisticsGroupFromCache(group)
128
+ }
97
129
98
130
[group :group, statistics :stats]
99
131
}
0 commit comments