-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
G11n java client - Fix for expiration-related issues (issues 604, 664, and 662) #699
Changes from all commits
f4c1214
1a4d95e
5e72b73
73d85ca
2a38107
c7f5d4f
056891a
a2ba98b
17978bd
6c69170
41a8aa1
dd2455d
e26d372
01ab69e
750c9d2
529e5cc
b40fcf9
fa123f9
30b2054
0c69787
ffc0a74
d8450e4
d7f7641
0dcd827
d06adb3
48c8558
4e45bed
6a40dc8
4c3c12d
7e0d939
baef0d0
02a96e9
78d88f7
655c223
8746915
897a03c
3a4e183
b08feb4
8f57e24
72c8799
92edfc8
2042554
ae707ff
82818d5
31d4ff6
c9f83d7
78dcc1c
9281c64
2657762
9e80f09
68467f3
d67c72e
8591973
22a5b29
ce2488b
1092963
35d1d97
ada17f1
fc60eef
3e70449
609d8e4
dd19b1c
7389f8b
5b92e44
dfb41b5
21a7105
84343d5
be8e443
b805d36
17bc16c
15da010
631de86
ffd9adb
c00f7ae
d0504c4
79b18d0
ec9f5db
2e8bd36
4e44a8c
de5c4ee
406cb2a
c80696a
79d111e
24d8d5d
23e62b6
369643f
4e67e25
636b5b4
71a87e5
b9a90e3
060666d
52e3695
2858ebc
bbe3047
fa56388
08cd96d
e89ff56
119aebd
e8a937d
9d95cd1
06b60bb
51b4dfa
778a7a1
077f19b
9424346
c25a676
db641b9
5cd06d6
6915f11
a64be3f
7128194
c4a388c
5ecea93
4c5b0a1
067d19d
711ce80
b7dabdb
d8543ec
e8094f4
9a5ef77
5011c38
33432b1
c9d3cae
adb40ec
7ab5c59
3c10089
76105b7
49c5648
d543e99
3ecf958
e6a07a1
89ff088
f1356ab
c63bc2d
beab148
2cd7713
e13b694
f1f70b3
fe535dc
0d22b9f
4870782
f451436
89d7b0d
ec6e693
65f1d08
60fb3b3
a698f20
eede3b5
2dbfc72
e63ab94
ae1dccd
6fbaab4
ff99a37
271ee08
230e21c
55764a7
e9214cb
beaa5ef
673b8fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,37 +32,43 @@ public ComponentService(MessagesDTO dto) { | |
this.dto = dto; | ||
} | ||
|
||
/** | ||
* Fetch messages from either remote vip service or local bundle | ||
* | ||
* @param cacheItem MessageCacheItem object to store the messages | ||
* @param msgSourceQueueIter Iterator of the msgSourceQueue (e.g. [DataSourceEnum.VIP, DataSourceEnum.Bundle]) | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
private void fetchMessages(final MessageCacheItem cacheItem, Iterator<DataSourceEnum> msgSourceQueueIter) { | ||
if (!msgSourceQueueIter.hasNext()) | ||
return; | ||
|
||
long timestampOld = cacheItem.getTimestamp(); | ||
DataSourceEnum dataSource = msgSourceQueueIter.next(); | ||
dataSource.createMessageOpt(dto).getComponentMessages(cacheItem); | ||
long timestamp = cacheItem.getTimestamp(); | ||
if (timestampOld == timestamp) { | ||
logger.debug(FormatUtils.format(ConstantsMsg.GET_MESSAGES_FAILED, dto.getComponent(), dto.getLocale(), dataSource.toString())); | ||
} | ||
|
||
// Skip this block if timestamp is not 0 (which means cacheItem is in the cache) regardless if cacheItem is expired or not. | ||
// Otherwise, try the next dataSource in the queue. | ||
if (timestamp == 0) { | ||
// Try the next dataSource in the queue | ||
if (msgSourceQueueIter.hasNext()) { | ||
fetchMessages(cacheItem, msgSourceQueueIter); | ||
// If no more data source in queue, log the error. This means that neither online nor offline fetch succeeded. | ||
} else { | ||
logger.debug(FormatUtils.format(ConstantsMsg.GET_MESSAGES_FAILED_ALL, dto.getComponent(), dto.getLocale())); | ||
} | ||
} | ||
} | ||
/** | ||
* Fetches data and populates the MessageCacheItem | ||
* | ||
* @param cacheItem The MessageCacheItem to populate/refresh data in. The following properties of cacheItem will be populated/refreshed: | ||
* <ul> | ||
* <li>The cachedData map which holds the localized messages</li> | ||
* <li>The timestamp of when the messages were fetched</li> | ||
* <li>The maxAgeMillis which tells how long before the cacheData map is considered to be expired.</li> | ||
* <li>The eTag, if any, which will be used in the succeeding cache refresh.</li> | ||
* </ul> | ||
* @param msgSourceQueueIter Iterator of the message source queue (e.g. [DataSourceEnum.VIP, DataSourceEnum.Bundle]) | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
private void refreshCacheItem(final MessageCacheItem cacheItem, Iterator<DataSourceEnum> msgSourceQueueIter) { | ||
if (!msgSourceQueueIter.hasNext()) | ||
return; | ||
|
||
long timestampOld = cacheItem.getTimestamp(); | ||
DataSourceEnum dataSource = msgSourceQueueIter.next(); | ||
dataSource.createMessageOpt(dto).getComponentMessages(cacheItem); | ||
long timestamp = cacheItem.getTimestamp(); | ||
if (timestampOld == timestamp) { | ||
logger.debug(FormatUtils.format(ConstantsMsg.GET_MESSAGES_FAILED, dto.getComponent(), dto.getLocale(), dataSource.toString())); | ||
} | ||
|
||
// Skip this block if timestamp is not 0 (which means cacheItem is in the cache) regardless if cacheItem is expired or not. | ||
// Otherwise, try the next dataSource in the queue. | ||
if (timestamp == 0) { | ||
// Try the next dataSource in the queue | ||
if (msgSourceQueueIter.hasNext()) { | ||
refreshCacheItem(cacheItem, msgSourceQueueIter); | ||
// If no more data source in queue, log the error. This means that neither online nor offline fetch succeeded. | ||
} else { | ||
logger.debug(FormatUtils.format(ConstantsMsg.GET_MESSAGES_FAILED_ALL, dto.getComponent(), dto.getLocale())); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Get MessageCacheItem from cache. | ||
|
@@ -100,44 +106,78 @@ public MessageCacheItem getMessages(Iterator<Locale> fallbackLocalesIter) { | |
if (cacheService.isContainComponent()) { // Item is in cache | ||
cacheItem = cacheService.getCacheOfComponent(); | ||
if (cacheItem.isExpired()) { // cacheItem has expired | ||
// Update the cache in a separate thread | ||
populateCacheTask(cacheItem); | ||
// Refresh the cacheItem in a separate thread | ||
refreshCacheItemTask(cacheItem); | ||
} | ||
} else { // Item is not in cache | ||
// Create a new cacheItem object to be stored in cache | ||
cacheItem = new MessageCacheItem(); | ||
fetchMessages(cacheItem, VIPCfg.getInstance().getMsgOriginsQueue().iterator()); | ||
|
||
if (!cacheItem.getCachedData().isEmpty()) { | ||
cacheService.addCacheOfComponent(cacheItem); | ||
} else if (!dto.getLocale().equals(ConstantsKeys.SOURCE) && fallbackLocalesIter!=null && fallbackLocalesIter.hasNext()) { | ||
// If failed to fetch message, use MessageCacheItem of the next fallback locale. | ||
MessagesDTO fallbackLocaleDTO = new MessagesDTO(dto.getComponent(), fallbackLocalesIter.next().toLanguageTag(), dto.getProductID(), dto.getVersion()); | ||
cacheItem = new ComponentService(fallbackLocaleDTO).getMessages(fallbackLocalesIter); | ||
if (!cacheItem.getCachedData().isEmpty()) { | ||
cacheService.addCacheOfComponent(cacheItem); | ||
} | ||
// If the cacheItem is for a fallback locale, create and store cacheItem for the requested locale in a separate thread. | ||
if (!LocaleUtility.isSameLocale(cacheItem.getLocale(), this.dto.getLocale())) { | ||
this.createCacheItemTask(fallbackLocalesIter); | ||
} | ||
Comment on lines
+112
to
115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about moving this into above if block so it doesn't fetch every time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We actually want to always try to fetch for the requested locale every time in a separate thread, not just when the cacheItem has expired. This is because if the cacheItem is for a fallback locale, any other call for a non-supported locale can trigger a refresh of the fallback locale's cacheItem. If that happens, the requested locale may not satisfy the |
||
} else { // Item is not in cache. Create and store cacheItem for the requested locale | ||
cacheItem = createCacheItem(fallbackLocalesIter); | ||
} | ||
return cacheItem; | ||
} | ||
|
||
private void populateCacheTask(MessageCacheItem cacheItem) { | ||
|
||
/** | ||
* Creates a new MessageCacheItem for the DTO and stores it in cache. | ||
* | ||
* @param fallbackLocalesIter The fallback locale queue to use in case of failure. | ||
* | ||
*/ | ||
private MessageCacheItem createCacheItem(Iterator<Locale> fallbackLocalesIter) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems here has a recursive call: getMessages(Iterator fallbackLocalesIter) -> createCacheItem(Iterator fallbackLocalesIter) -> new ComponentService(fallbackLocaleDTO).getMessages(fallbackLocalesIter) In my opinion, the hierarchical relation between methods are not clear. |
||
CacheService cacheService = new CacheService(dto); | ||
// Create a new cacheItem object to be stored in cache | ||
MessageCacheItem cacheItem = new MessageCacheItem(); | ||
refreshCacheItem(cacheItem, VIPCfg.getInstance().getMsgOriginsQueue().iterator()); | ||
|
||
if (!cacheItem.getCachedData().isEmpty()) { | ||
cacheService.addCacheOfComponent(cacheItem); | ||
} else if (!dto.getLocale().equals(ConstantsKeys.SOURCE) && fallbackLocalesIter!=null && fallbackLocalesIter.hasNext()) { | ||
// If failed to fetch message for the requetsed DTO, use MessageCacheItem of the next fallback locale. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to judge if current fallback locale is same as the locale in DTO. If same, should skip it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you follow the recursive logic, the locale in the dto will be the previous locale in the fallback locale queue. |
||
MessagesDTO fallbackLocaleDTO = new MessagesDTO(dto.getComponent(), fallbackLocalesIter.next().toLanguageTag(), dto.getProductID(), dto.getVersion()); | ||
cacheItem = new ComponentService(fallbackLocaleDTO).getMessages(fallbackLocalesIter); | ||
if (!cacheItem.getCachedData().isEmpty()) { | ||
cacheService.addCacheOfComponent(cacheItem); | ||
} | ||
Comment on lines
+136
to
+142
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here only use the first fallback locale in the fallback locale chain, doesn't go through the whole fallback locale chain. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It actually does go through the whole cahin/queue. In line 138, you see |
||
} | ||
return cacheItem; | ||
} | ||
|
||
private void createCacheItemTask(Iterator<Locale> fallbackLocalesIter) { | ||
Callable<MessageCacheItem> callable = () -> { | ||
try { | ||
return this.createCacheItem(fallbackLocalesIter); | ||
} catch (Exception e) { | ||
// To make sure that the thread will close | ||
// even when an exception is thrown | ||
return null; | ||
} | ||
}; | ||
FutureTask<MessageCacheItem> task = new FutureTask<>(callable); | ||
Thread thread = new Thread(task); | ||
thread.start(); | ||
} | ||
|
||
private void refreshCacheItemTask(MessageCacheItem cacheItem) { | ||
Callable<MessageCacheItem> callable = () -> { | ||
try { | ||
// Pass cacheItem to getMessages so that: | ||
// 1. A previously stored etag, if any, can be used for the next HTTP request. | ||
// 2. CacheItem properties such as etag, timestamp and maxAgeMillis can be refreshed | ||
// with new properties from the next HTTP response. | ||
fetchMessages(cacheItem, VIPCfg.getInstance().getMsgOriginsQueue().listIterator()); | ||
return cacheItem; | ||
// Get the locale of the cacheItem object. It may not be the same as the requested DTO's locale (e.g. the cacheItem is for a fallback locale). | ||
String cacheItemLocale = cacheItem.getLocale(); | ||
|
||
// Refresh the properties of the cacheItem accordingly by passing a DTO with the correct locale | ||
// to ComponentService, so that it will fetch messages for the correct locale to refresh the cacheItem. | ||
MessagesDTO cacheItemDTO = new MessagesDTO(dto.getComponent(), cacheItemLocale, dto.getProductID(), dto.getVersion()); | ||
new ComponentService(cacheItemDTO).refreshCacheItem(cacheItem, VIPCfg.getInstance().getMsgOriginsQueue().listIterator()); | ||
|
||
return cacheItem; | ||
} catch (Exception e) { | ||
// To make sure that the thread will close | ||
// even when an exception is thrown | ||
return null; | ||
} | ||
}; | ||
FutureTask<MessageCacheItem> task = new FutureTask<MessageCacheItem>(callable); | ||
FutureTask<MessageCacheItem> task = new FutureTask<>(callable); | ||
Thread thread = new Thread(task); | ||
thread.start(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need to store time to cache because data from local will never expire.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Data from local must expire. Use case: If data from local was fetched as a fallback for when Singleton service is temporarily down. In this case, when the cached data from local expires, it should try to fetch from the service again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be set the time in other place because here is Local Opt, it doesn't need to consider the time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So where do you recommend to set the time? By the way, there is nothing about expire time here, only timestamp of when the data was fetched from local bundles. And it has been there before this PR, just combined them into 1 call, so I do not want to make any change about that in this PR.