Skip to content

Commit cda19ff

Browse files
deepprakash345Deep Prakash Dewanji
andauthored
feat: adding caching of supported submit action api response (#1547)
* feat: adding caching of supported submit action api response * fix: moved Caching logic to a separate class * fix: added javadocs --------- Co-authored-by: Deep Prakash Dewanji <deepprakashdewanji@labusers-mbp-9.corp.adobe.com>
1 parent 1797d8c commit cda19ff

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2+
~ Copyright 2025 Adobe
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
16+
package com.adobe.cq.forms.core.components.util;
17+
18+
import java.util.List;
19+
import java.util.Map;
20+
import java.util.concurrent.ConcurrentHashMap;
21+
import java.util.concurrent.TimeUnit;
22+
23+
public class CacheManager {
24+
/**
25+
* Cache to store submit actions lists indexed by cache key
26+
*/
27+
private static final Map<String, List<String>> SUBMIT_ACTIONS_CACHE = new ConcurrentHashMap<>();
28+
29+
/**
30+
* Cache to store timestamps when entries were added, indexed by cache key
31+
*/
32+
private static final Map<String, Long> CACHE_TIMESTAMPS = new ConcurrentHashMap<>();
33+
34+
/**
35+
* Time-to-live duration for cache entries (24 hours)
36+
*/
37+
private static final long CACHE_TTL = TimeUnit.HOURS.toMillis(24);
38+
39+
/**
40+
* Cache key for storing supported submit actions
41+
*/
42+
public static final String SUPPORTED_SUBMIT_ACTIONS_CACHE_KEY = "supportedsubmit_actions";
43+
44+
/**
45+
* Retrieves a value from the cache if it exists and has not expired
46+
*
47+
* @param cacheKey The key to look up in the cache
48+
* @return The cached List<String> if found and valid, null if not found or
49+
* expired
50+
*/
51+
public static List<String> getFromCache(String cacheKey) {
52+
Long timestamp = CACHE_TIMESTAMPS.get(cacheKey);
53+
if (timestamp == null) {
54+
return null;
55+
}
56+
57+
if (System.currentTimeMillis() - timestamp > CACHE_TTL) {
58+
SUBMIT_ACTIONS_CACHE.remove(cacheKey);
59+
CACHE_TIMESTAMPS.remove(cacheKey);
60+
return null;
61+
}
62+
63+
return SUBMIT_ACTIONS_CACHE.get(cacheKey);
64+
}
65+
66+
/**
67+
* Stores a value in the cache with the current timestamp
68+
*
69+
* @param cacheKey The key under which to store the value
70+
* @param value The List<String> value to cache
71+
*/
72+
public static void putInCache(String cacheKey, List<String> value) {
73+
SUBMIT_ACTIONS_CACHE.put(cacheKey, value);
74+
CACHE_TIMESTAMPS.put(cacheKey, System.currentTimeMillis());
75+
}
76+
}

bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/ComponentUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,11 @@ public static boolean shouldIncludeSubmitProperties(SlingHttpServletRequest requ
302302
* request fails
303303
*/
304304
public static List<String> getSupportedSubmitActions(HttpClientBuilderFactory clientBuilderFactory) {
305+
// Check cache first
306+
List<String> cachedActions = CacheManager.getFromCache(CacheManager.SUPPORTED_SUBMIT_ACTIONS_CACHE_KEY);
307+
if (cachedActions != null) {
308+
return cachedActions;
309+
}
305310
String supportedSubmitActionsUrl = "https://forms.adobe.com/adobe/forms/af/submit";
306311
List<String> supportedSubmitActions = new ArrayList<>();
307312
if (clientBuilderFactory == null) {
@@ -336,6 +341,7 @@ public static List<String> getSupportedSubmitActions(HttpClientBuilderFactory cl
336341
} catch (Exception e) {
337342
logger.error("Error while fetching supported submit actions", e);
338343
}
344+
CacheManager.putInCache(CacheManager.SUPPORTED_SUBMIT_ACTIONS_CACHE_KEY, supportedSubmitActions);
339345
return supportedSubmitActions;
340346
}
341347

bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/ComponentUtilsTest.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@
1515
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
1616
package com.adobe.cq.forms.core.components.util;
1717

18+
import java.lang.reflect.Field;
1819
import java.util.HashMap;
20+
import java.util.List;
1921
import java.util.Map;
2022

23+
import org.apache.http.StatusLine;
24+
import org.apache.http.client.methods.CloseableHttpResponse;
25+
import org.apache.http.entity.StringEntity;
26+
import org.apache.http.impl.client.CloseableHttpClient;
27+
import org.apache.http.impl.client.HttpClientBuilder;
28+
import org.apache.http.osgi.services.HttpClientBuilderFactory;
2129
import org.apache.jackrabbit.JcrConstants;
2230
import org.apache.sling.api.resource.Resource;
2331
import org.apache.sling.api.resource.ResourceResolver;
@@ -29,6 +37,11 @@
2937
import com.adobe.cq.forms.core.components.internal.form.FormConstants;
3038

3139
import static org.junit.jupiter.api.Assertions.*;
40+
import static org.mockito.ArgumentMatchers.any;
41+
import static org.mockito.Mockito.mock;
42+
import static org.mockito.Mockito.times;
43+
import static org.mockito.Mockito.verify;
44+
import static org.mockito.Mockito.when;
3245

3346
public class ComponentUtilsTest {
3447
@Test
@@ -96,4 +109,68 @@ public void testParseNumber() {
96109
assertNull(ComponentUtils.parseNumber(null));
97110
}
98111

112+
@Test
113+
public void testSubmitActionsCaching() throws Exception {
114+
// Clear cache before test
115+
resetCache();
116+
117+
// Set up mocks
118+
HttpClientBuilderFactory mockClientFactory = mock(HttpClientBuilderFactory.class);
119+
HttpClientBuilder mockHttpClientBuilder = mock(HttpClientBuilder.class);
120+
CloseableHttpClient mockHttpClient = mock(CloseableHttpClient.class);
121+
CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class);
122+
StatusLine mockStatusLine = mock(StatusLine.class);
123+
124+
// Configure mock response
125+
when(mockStatusLine.getStatusCode()).thenReturn(200);
126+
when(mockResponse.getStatusLine()).thenReturn(mockStatusLine);
127+
when(mockResponse.getEntity()).thenReturn(
128+
new StringEntity("{\"supported\":[\"spreadsheet\",\"email\"]}"));
129+
130+
// Wire up mock chain
131+
when(mockClientFactory.newBuilder()).thenReturn(mockHttpClientBuilder);
132+
when(mockHttpClientBuilder.build()).thenReturn(mockHttpClient);
133+
when(mockHttpClient.execute(any())).thenReturn(mockResponse);
134+
135+
// First call should execute HTTP request
136+
List<String> actions1 = ComponentUtils.getSupportedSubmitActions(mockClientFactory);
137+
138+
// Second call should use cached result
139+
List<String> actions2 = ComponentUtils.getSupportedSubmitActions(mockClientFactory);
140+
141+
// Verify HTTP call only happened once
142+
verify(mockHttpClient, times(1)).execute(any());
143+
144+
// Verify returned actions are the same
145+
assertEquals(actions1, actions2);
146+
assertEquals(2, actions1.size());
147+
assertTrue(actions1.contains("spreadsheet"));
148+
assertTrue(actions1.contains("email"));
149+
150+
// Simulate TTL expiry and verify HTTP call happens again
151+
setCacheTimestampToExpired();
152+
List<String> actions3 = ComponentUtils.getSupportedSubmitActions(mockClientFactory);
153+
verify(mockHttpClient, times(2)).execute(any());
154+
}
155+
156+
// Helper methods to access private cache fields via reflection
157+
private void resetCache() throws Exception {
158+
Field cacheField = CacheManager.class.getDeclaredField("SUBMIT_ACTIONS_CACHE");
159+
cacheField.setAccessible(true);
160+
((Map) cacheField.get(null)).clear();
161+
162+
Field timestampsField = CacheManager.class.getDeclaredField("CACHE_TIMESTAMPS");
163+
timestampsField.setAccessible(true);
164+
((Map) timestampsField.get(null)).clear();
165+
}
166+
167+
private void setCacheTimestampToExpired() throws Exception {
168+
Field timestampsField = CacheManager.class.getDeclaredField("CACHE_TIMESTAMPS");
169+
timestampsField.setAccessible(true);
170+
Map<String, Long> timestamps = (Map<String, Long>) timestampsField.get(null);
171+
172+
// Set timestamp to 25 hours ago (beyond TTL of 24 hours)
173+
timestamps.put(CacheManager.SUPPORTED_SUBMIT_ACTIONS_CACHE_KEY,
174+
System.currentTimeMillis() - 25 * 60 * 60 * 1000);
175+
}
99176
}

0 commit comments

Comments
 (0)