5252import java .util .HashSet ;
5353import java .util .Iterator ;
5454import java .util .List ;
55+ import java .util .Map ;
5556import java .util .Set ;
57+ import java .util .concurrent .ConcurrentHashMap ;
5658
5759import edu .umd .cs .findbugs .annotations .Nullable ;
5860import edu .umd .cs .findbugs .annotations .SuppressFBWarnings ;
@@ -104,6 +106,20 @@ public class BrokerOAuth2TokenCache
104106 private final MicrosoftFamilyOAuth2TokenCache mFociCache ;
105107 private final int mUid ;
106108 private ProcessUidCacheFactory mDelegate = null ;
109+ /**
110+ * Shared, process-wide registry of in-memory augmented account/credential caches keyed by
111+ * storage (SharedPreferences) name.
112+ * <p>
113+ * Populated lazily via computeIfAbsent in getCacheToBeUsed(...). Allows multiple
114+ * BrokerOAuth2TokenCache instances to reuse the same in-memory layer for the same underlying
115+ * encrypted name-value store, reducing disk I/O and serialization overhead.
116+ * <p>
117+ * Thread-safety: ConcurrentHashMap ensures safe concurrent access and publication.
118+ * Lifecycle: Entries are never removed for the lifetime of the process.
119+ */
120+ private static final Map <String , SharedPreferencesAccountCredentialCacheWithMemoryCache >
121+ inMemoryCacheMapByStorage = new ConcurrentHashMap <>();
122+
107123
108124 /**
109125 * Constructs a new BrokerOAuth2TokenCache.
@@ -1337,6 +1353,7 @@ public void clearAll() {
13371353
13381354 this .mFociCache .clearAll ();
13391355 this .mApplicationMetadataCache .clear ();
1356+ inMemoryCacheMapByStorage .clear ();
13401357 }
13411358
13421359 /**
@@ -1611,14 +1628,10 @@ private MsalOAuth2TokenCache initializeProcessUidCache(@NonNull final IPlatformC
16111628 return mDelegate .getTokenCache (components , uid );
16121629 }
16131630
1614- final INameValueStorage <String > sharedPreferencesFileManager =
1615- components .getStorageSupplier ().getEncryptedNameValueStore (
1616- SharedPreferencesAccountCredentialCache
1617- .getBrokerUidSequesteredFilename (uid ),
1618- String .class
1619- );
1631+ final String storeName = SharedPreferencesAccountCredentialCache
1632+ .getBrokerUidSequesteredFilename (uid );
16201633
1621- return getTokenCache (components , sharedPreferencesFileManager , false );
1634+ return getTokenCache (components , storeName , false );
16221635 }
16231636
16241637 private static MicrosoftFamilyOAuth2TokenCache initializeFociCache (@ NonNull final IPlatformComponents components ) {
@@ -1628,37 +1641,16 @@ private static MicrosoftFamilyOAuth2TokenCache initializeFociCache(@NonNull fina
16281641 "Initializing foci cache"
16291642 );
16301643
1631- final INameValueStorage <String > sharedPreferencesFileManager =
1632- components .getStorageSupplier ().getEncryptedNameValueStore (
1633- SharedPreferencesAccountCredentialCache .BROKER_FOCI_ACCOUNT_CREDENTIAL_SHARED_PREFERENCES ,
1634- String .class
1635- );
1644+ final String storeName = SharedPreferencesAccountCredentialCache .BROKER_FOCI_ACCOUNT_CREDENTIAL_SHARED_PREFERENCES ;
16361645
1637- return getTokenCache (components , sharedPreferencesFileManager , true );
1646+ return getTokenCache (components , storeName , true );
16381647 }
16391648
16401649 @ SuppressWarnings (UNCHECKED )
16411650 private static <T extends MsalOAuth2TokenCache > T getTokenCache (@ NonNull final IPlatformComponents components ,
1642- @ NonNull final INameValueStorage < String > spfm ,
1651+ String storeName ,
16431652 boolean isFoci ) {
1644- final ICacheKeyValueDelegate cacheKeyValueDelegate = new CacheKeyValueDelegate ();
1645- final boolean isFlightEnabled = CommonFlightsManager .INSTANCE
1646- .getFlightsProvider ()
1647- .isFlightEnabled (CommonFlight .USE_IN_MEMORY_CACHE_FOR_ACCOUNTS_AND_CREDENTIALS );
1648- final IAccountCredentialCache accountCredentialCache ;
1649- if (isFlightEnabled ) {
1650- accountCredentialCache = new SharedPreferencesAccountCredentialCacheWithMemoryCache (
1651- cacheKeyValueDelegate ,
1652- spfm
1653- );
1654- SpanExtension .current ().setAttribute (AttributeName .in_memory_cache_used_for_accounts_and_credentials .name (), true );
1655- } else {
1656- accountCredentialCache = new SharedPreferencesAccountCredentialCache (
1657- cacheKeyValueDelegate ,
1658- spfm
1659- );
1660- SpanExtension .current ().setAttribute (AttributeName .in_memory_cache_used_for_accounts_and_credentials .name (), false );
1661- }
1653+ final IAccountCredentialCache accountCredentialCache = getCacheToBeUsed (components , storeName );
16621654 final MicrosoftStsAccountCredentialAdapter accountCredentialAdapter =
16631655 new MicrosoftStsAccountCredentialAdapter ();
16641656
@@ -1678,6 +1670,54 @@ private static <T extends MsalOAuth2TokenCache> T getTokenCache(@NonNull final I
16781670 );
16791671 }
16801672
1673+
1674+ /**
1675+ * Determines which cache implementation to use based on flighting.
1676+ * <p>
1677+ * When the flight {@code USE_IN_MEMORY_CACHE_FOR_ACCOUNTS_AND_CREDENTIALS} is enabled,
1678+ * returns a shared, cached instance of {@link SharedPreferencesAccountCredentialCacheWithMemoryCache}
1679+ * for the given storage, improving performance by reusing the in-memory cache layer across cache instances.
1680+ * When disabled, returns a new {@link SharedPreferencesAccountCredentialCache} instance.
1681+ * <p>
1682+ * Critical behavior: When flight is enabled, the same {@code SharedPreferencesAccountCredentialCacheWithMemoryCache}
1683+ * instance is returned for the same {@code spfm} reference, meaning the cache is shared across multiple
1684+ * {@code BrokerOAuth2TokenCache} instances.
1685+ * <p>
1686+ * Thread-safety: This method is thread-safe via {@code ConcurrentHashMap.computeIfAbsent}.
1687+ * <p>
1688+ * Lifecycle: Returned cached instances are never removed from the static map.
1689+ *
1690+ * @return A cached shared in-memory cache instance (flight enabled) or a new
1691+ * non-cached instance (flight disabled).
1692+ */
1693+ public static IAccountCredentialCache getCacheToBeUsed (@ NonNull final IPlatformComponents components ,
1694+ final String storeName ) {
1695+ final boolean isFlightEnabled = CommonFlightsManager .INSTANCE
1696+ .getFlightsProvider ()
1697+ .isFlightEnabled (CommonFlight .USE_IN_MEMORY_CACHE_FOR_ACCOUNTS_AND_CREDENTIALS );
1698+ SpanExtension .current ().setAttribute (AttributeName .in_memory_cache_used_for_accounts_and_credentials .name (), isFlightEnabled );
1699+ if (isFlightEnabled ) {
1700+ return inMemoryCacheMapByStorage .computeIfAbsent (storeName , s ->
1701+ new SharedPreferencesAccountCredentialCacheWithMemoryCache (
1702+ new CacheKeyValueDelegate (),
1703+ components .getStorageSupplier ().getEncryptedNameValueStore (
1704+ storeName ,
1705+ String .class
1706+ ))
1707+ );
1708+ } else {
1709+ final INameValueStorage <String > sharedPreferencesFileManager =
1710+ components .getStorageSupplier ().getEncryptedNameValueStore (
1711+ storeName ,
1712+ String .class
1713+ );
1714+ return new SharedPreferencesAccountCredentialCache (
1715+ new CacheKeyValueDelegate (),
1716+ sharedPreferencesFileManager
1717+ );
1718+ }
1719+ }
1720+
16811721 @ Nullable
16821722 private MsalOAuth2TokenCache getTokenCacheForClient (@ Nullable final BrokerApplicationMetadata metadata ) {
16831723 final String methodName = ":getTokenCacheForClient(bam)" ;
@@ -1736,53 +1776,4 @@ private MsalOAuth2TokenCache getTokenCacheForClient(@NonNull final String client
17361776
17371777 return getTokenCacheForClient (metadata );
17381778 }
1739-
1740- /**
1741- * Sets the SSO state for the supplied Account, relative to the provided uid.
1742- *
1743- * @param uidStr The uid of the app whose SSO token is being inserted.
1744- * @param account The account for which the supplied token is being inserted.
1745- * @param refreshToken The token to insert.
1746- */
1747- public void setSingleSignOnState (@ NonNull final String uidStr ,
1748- @ NonNull final GenericAccount account ,
1749- @ NonNull final GenericRefreshToken refreshToken ) {
1750- final String methodName = ":setSingleSignOnState" ;
1751-
1752- final boolean isFrt = refreshToken .getIsFamilyRefreshToken ();
1753-
1754- MsalOAuth2TokenCache targetCache ;
1755-
1756- final int uid = Integer .parseInt (uidStr );
1757-
1758- if (isFrt ) {
1759- Logger .verbose (TAG + methodName , "Saving tokens to foci cache." );
1760-
1761- targetCache = mFociCache ;
1762- } else {
1763- // If there is an existing cache for this client id, use it. Otherwise, create a new
1764- // one based on the supplied uid.
1765- targetCache = initializeProcessUidCache (getComponents (), uid );
1766- }
1767- try {
1768- targetCacheSetSingleSignOnState (account , refreshToken , targetCache );
1769- updateApplicationMetadataCache (
1770- refreshToken .getClientId (),
1771- refreshToken .getEnvironment (),
1772- refreshToken .getFamilyId (),
1773- uid
1774- );
1775- } catch (ClientException e ) {
1776- Logger .warn (
1777- TAG + methodName ,
1778- "Failed to save account/refresh token. Skipping."
1779- );
1780- }
1781- }
1782-
1783- // Suppressing unchecked warning as the generic type was not provided for targetCache
1784- @ SuppressWarnings (WarningType .unchecked_warning )
1785- private void targetCacheSetSingleSignOnState (@ NonNull GenericAccount account , @ NonNull GenericRefreshToken refreshToken , MsalOAuth2TokenCache targetCache ) throws ClientException {
1786- targetCache .setSingleSignOnState (account , refreshToken );
1787- }
17881779}
0 commit comments