Skip to content

Commit

Permalink
OKTA-526761: Add Caching support (#768)
Browse files Browse the repository at this point in the history
* add caching
  • Loading branch information
arvindkrishnakumar-okta authored Oct 6, 2022
1 parent 11657ec commit 44bed7a
Show file tree
Hide file tree
Showing 29 changed files with 2,451 additions and 56 deletions.
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,52 @@ okta.client.requestTimeout = 0
okta.client.rateLimit.maxRetries = 0
```

## Caching

By default, a simple production-grade in-memory CacheManager will be enabled when the Client instance is created. This CacheManager implementation has the following characteristics:

- It assumes a default time-to-live and time-to-idle of 1 hour for all cache entries.
- It auto-sizes itself based on your application's memory usage. It will not cause OutOfMemoryExceptions.

**The default cache manager is not suitable for an application deployed across multiple JVMs.**

This is because the default implementation is 100% in-memory (in-process) in the current JVM. If more than one JVM is deployed with the same application codebase - for example, a web application deployed on multiple identical hosts for scaling or high availability - each JVM would have it's own in-memory cache.

As a result, if your application that uses an Okta Client instance is deployed across multiple JVMs, you SHOULD ensure that the Client is configured with a CacheManager implementation that uses coherent and clustered/distributed memory.

See the [`ClientBuilder` Javadoc](https://developer.okta.com/okta-sdk-java/apidocs/com/okta/sdk/client/ClientBuilder) for more details on caching.

### Caching for applications deployed on a single JVM

If your application is deployed on a single JVM and you still want to use the default CacheManager implementation, but the default cache configuration does not meet your needs, you can specify a different configuration. For example:

[//]: # (method: complexCaching)
```java
Caches.newCacheManager()
.withDefaultTimeToLive(300, TimeUnit.SECONDS) // default
.withDefaultTimeToIdle(300, TimeUnit.SECONDS) //general default
.withCache(forResource(User.class) //User-specific cache settings
.withTimeToLive(1, TimeUnit.HOURS)
.withTimeToIdle(30, TimeUnit.MINUTES))
.withCache(forResource(Group.class) //Group-specific cache settings
.withTimeToLive(1, TimeUnit.HOURS))
//... etc ...
.build();
```
[//]: # (end: complexCaching)

### Disable Caching

While production applications will usually enable a working CacheManager as described above, you might wish to disable caching entirely. You can do this by configuring a disabled CacheManager instance. For example:

[//]: # (method: disableCaching)
```java
ApiClient client = Clients.builder()
.setCacheManager(Caches.newDisabledCacheManager())
.build();
```
[//]: # (end: disableCaching)

## Building the SDK

In most cases, you won't need to build the SDK from source. If you want to build it yourself, take a look at the [build instructions wiki](https://github.com/okta/okta-sdk-java/wiki/Build-It) (though just cloning the repo and running `mvn install` should get you going).
Expand Down
2 changes: 1 addition & 1 deletion THIRD-PARTY-NOTICES
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ This project includes:
Jackson-Datatype-ThreeTenBackport under The Apache Software License, Version 2.0
Jackson-JAXRS: base under The Apache Software License, Version 2.0
Jackson-JAXRS: JSON under The Apache Software License, Version 2.0
Jakarta Activation API jar under EDL 1.0
Jakarta Bean Validation API under Apache License 2.0
Jakarta XML Binding API under Eclipse Distribution License - v 1.0
Java Native Access under LGPL, version 2.1 or Apache License v2.0
JavaBeans Activation Framework API jar under EDL 1.0
JavaMail API (no providers) under CDDL/GPLv2+CE
javax.annotation API under CDDL + GPLv2 with classpath exception
JJWT :: API under Apache License, Version 2.0
Expand Down
2 changes: 1 addition & 1 deletion api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>6.0.1</version>
<version>6.2.0</version>
<executions>
<execution>
<goals>
Expand Down
57 changes: 57 additions & 0 deletions api/src/main/java/com/okta/sdk/cache/Cache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2014 Stormpath, Inc.
* Modifications Copyright 2018 Okta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.okta.sdk.cache;

/**
* A Cache efficiently stores temporary objects primarily to improve an application's performance.
* <p>
* This interface provides an abstraction (wrapper) API on top of an underlying
* cache framework's cache instance (e.g. JCache, Ehcache, Hazelcast, JCS, OSCache, JBossCache, TerraCotta, Coherence,
* GigaSpaces, etc, etc), allowing a Okta SDK user to configure any cache mechanism they choose.
*
* @since 0.5.0
*/
public interface Cache<K, V> {

/**
* Returns the cached value stored under the specified {@code key} or
* {@code null} if there is no cache entry for that {@code key}.
*
* @param key the key that the value was previous added with
* @return the cached object or {@code null} if there is no entry for the specified {@code key}
*/
V get(K key);


/**
* Adds a cache entry.
*
* @param key the key used to identify the object being stored.
* @param value the value to be stored in the cache.
* @return the previous value associated with the given {@code key} or {@code null} if there was no previous value
*/
V put(K key, V value);


/**
* Removes the cached value stored under the specified {@code key}.
*
* @param key the key used to identify the object being stored.
* @return the removed value or {@code null} if there was no value cached.
*/
V remove(K key);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2014 Stormpath, Inc.
* Modifications Copyright 2018 Okta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.okta.sdk.cache;

import java.util.concurrent.TimeUnit;

/**
* A Builder to specify configuration for {@link Cache} regions. This is usually used while building a CacheManager via
* the {@link CacheManagerBuilder}. CacheConfigurationBuilders can be constructed with the {@link Caches Caches}
* utility class. For example:
* <pre>
* Caches.named("cacheRegionNameHere")
* .{@link #withTimeToLive(long, TimeUnit) withTimeToLive(1, TimeUnit.DAYS)}
* .{@link #withTimeToIdle(long, TimeUnit) withTimeToIdle(2, TimeUnit.HOURS)};
* </pre>
* or
* <pre>
* Caches.forResource(Account.class)
* .{@link #withTimeToLive(long, TimeUnit) withTimeToLive(1, TimeUnit.DAYS)}
* .{@link #withTimeToIdle(long, TimeUnit) withTimeToIdle(2, TimeUnit.HOURS)};
* </pre>
*
* @see #withTimeToLive(long, TimeUnit)
* @see #withTimeToIdle(long, TimeUnit)
* @see Caches#forResource(Class)
* @see Caches#named(String)
* @since 0.5.0
*/
public interface CacheConfigurationBuilder {

/**
* Sets the associated {@code Cache} region's entry Time to Live (TTL).
* <p>
* Time to Live is the amount of time a cache entry may exist after first being created before it will expire and no
* longer be available. If a cache entry ever becomes older than this amount of time (regardless of how often
* it is accessed), it will be removed from the cache as soon as possible.
* <p>
* If this value is not configured, it is assumed that the Cache's entries could potentially live indefinitely.
* Note however that entries can still be expunged due to other conditions (e.g. memory constraints, Time to
* Idle setting, etc).
* <b>Usage</b>
* <pre>
* ...withTimeToLive(30, TimeUnit.MINUTES)...
* ...withTimeToLive(1, TimeUnit.HOURS)...
* </pre>
*
* @param ttl Time To Live scalar value
* @param ttlTimeUnit Time to Live unit of time
* @return the associated {@code Cache} region's entry Time to Live (TTL).
*/
CacheConfigurationBuilder withTimeToLive(long ttl, TimeUnit ttlTimeUnit);

/**
* Sets the associated {@code Cache} region's entry Time to Idle (TTI).
* <p>
* Time to Idle is the amount of time a cache entry may be idle (unused / not accessed) before it will expire and
* no longer be available. If a cache entry is not accessed at all after this amount of time, it will be removed
* from the cache as soon as possible.
* <p>
* If this value is not configured, it is assumed that the Cache's entries could potentially live indefinitely.
* Note however that entries can still be expunged due to other conditions (e.g. memory constraints, Time to
* Live setting, etc).
* <b>Usage</b>
* <pre>
* ...withTimeToIdle(30, TimeUnit.MINUTES)...
* ...withTimeToIdle(1, TimeUnit.HOURS)...
* </pre>
*
* @param tti Time To Idle scalar value
* @param ttiTimeUnit Time to Idle unit of time
* @return the associated {@code Cache} region's entry Time to Idle (TTI).
*/
CacheConfigurationBuilder withTimeToIdle(long tti, TimeUnit ttiTimeUnit);

}
40 changes: 40 additions & 0 deletions api/src/main/java/com/okta/sdk/cache/CacheManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2014 Stormpath, Inc.
* Modifications Copyright 2018 Okta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.okta.sdk.cache;

/**
* A CacheManager provides and maintains the lifecycle of {@link Cache Cache} instances.
* <p>
* This interface provides an abstraction (wrapper) API on top of an underlying
* cache framework's main Manager component (e.g. JCache, Ehcache, Hazelcast, JCS, OSCache, JBossCache, TerraCotta,
* Coherence, GigaSpaces, etc, etc), allowing a Okta SDK user to configure any cache mechanism they choose.
*
* @since 0.5.0
*/
public interface CacheManager {

/**
* Acquires the cache with the specified {@code name}. If a cache does not yet exist with that name, a new one
* will be created with that name and returned.
*
* @param name the name of the cache to acquire.
* @param <K> type of cache key
* @param <V> type of cache value
* @return the Cache with the given name
*/
<K, V> Cache<K, V> getCache(String name);
}
123 changes: 123 additions & 0 deletions api/src/main/java/com/okta/sdk/cache/CacheManagerBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright 2014 Stormpath, Inc.
* Modifications Copyright 2018 Okta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.okta.sdk.cache;

import java.util.concurrent.TimeUnit;

/**
* Builder for creating simple {@link CacheManager} instances <b>suitable for SINGLE-JVM APPLICATIONS</b>. If your
* application is deployed (mirrored or clustered) across multiple JVMs, you might not
* want to use this builder and use your own clusterable CacheManager implementation instead. See Clustering below.
* <b>Clustering</b>
* <b>The default CacheManager instances created by this Builder DO NOT SUPPORT CLUSTERING</b>.
* <p>
* If you use this Builder and your application is deployed on multiple JVMs, <b>each of your application instances will
* have <em>their own</em> local cache of Okta data</b>. Depending on your application requirements, and your
* cache TTL and TTI settings, this could introduce a significant difference in cached data seen by your application
* instances, which would likely impact user management behavior. For example, one application instance could see an
* account as ENABLED, but the other application instance could see it as DISABLED.
* <p>
* For some applications, this discrepancy might be an acceptable trade-off, especially if you configure
* {@link #withDefaultTimeToIdle(long, TimeUnit) timeToIdle} and
* {@link #withDefaultTimeToLive(long, TimeUnit) timeToLive} settings low enough. For example,
* maybe a TTL of 5 or 10 minutes is an acceptable time to see 'stale' account data. For other applications, this might
* not be acceptable. If it is acceptable, configuring the timeToIdle and timeToLive settings will allow you to
* fine-tune how much variance you allow.
* <p>
* However, if you are concerned about this difference in data and you want the Okta SDK's cache to be coherent
* across your application nodes (typically a good thing to have), it is strongly recommended that you do not use this
* Builder and instead configure the Okta SDK with a clustered {@code CacheManager} implementation of your choosing.
* This approach still gives you excellent performance improvements and ensures that your cached data is coherent (seen
* as the same) across all of your application instances.
* <p>
* This comes with an increased cost of course: setting up a caching product and/or cluster. However, this is not
* much of a problem in practice: most multi-instance applications already leverage caching clusters for their own
* application needs. In these environments, and with a proper {@code CacheManager} implementation leveraging a
* clustered cache, the Okta Java SDK will live quite happily using this same caching infrastructure.
* <p>
* A coherent cache deployment ensures all of your application instances/nodes can utilize the same cache policy and
* see the same cached security/identity data. Some example clustered caching solutions: Hazelcast,
* Ehcache+Terracotta, Memcache, Redis, Coherence, GigaSpaces, etc.
*
* @since 0.5.0
*/
public interface CacheManagerBuilder {

/**
* Sets the default Time to Live (TTL) for all cache regions managed by the {@link #build() built}
* {@code CacheManager}. You may override this default for individual cache regions by using the
* {@link #withCache(CacheConfigurationBuilder) withCache} for each region you wish to configure.
* <p>
* Time to Live is the amount of time a cache entry may exist after first being created before it will expire and no
* longer be available. If a cache entry ever becomes older than this amount of time (regardless of how often
* it is accessed), it will be removed from the cache as soon as possible.
* <p>
* If this value is not configured, it is assumed that cache entries could potentially live indefinitely.
* Note however that entries can still be expunged due to other conditions (e.g. memory constraints, Time to
* Idle setting, etc).
* <b>Usage</b>
* <pre>
* ...withDefaultTimeToLive(30, TimeUnit.MINUTES)...
* ...withDefaultTimeToLive(1, TimeUnit.HOURS)...
* </pre>
*
* @param ttl default Time To Live scalar value
* @param timeUnit default Time to Live unit of time
* @return the builder instance for method chaining.
*/
CacheManagerBuilder withDefaultTimeToLive(long ttl, TimeUnit timeUnit);

/**
* Sets the default Time to Idle (TTI) for all cache regions managed by the {@link #build() built}
* {@code CacheManager}. You may override this default for individual cache regions by using the
* {@link #withCache(CacheConfigurationBuilder) withCache} for each region you wish to configure.
* <p>
* Time to Idle is the amount of time a cache entry may be idle (unused / not accessed) before it will expire and
* no longer be available. If a cache entry is not accessed at all after this amount of time, it will be removed
* from the cache as soon as possible.
* <p>
* If this value is not configured, it is assumed that cache entries could potentially live indefinitely.
* Note however that entries can still be expunged due to other conditions (e.g. memory constraints, Time to
* Live setting, etc).
* <b>Usage</b>
* <pre>
* ...withDefaultTimeToLive(30, TimeUnit.MINUTES)...
* ...withDefaultTimeToLive(1, TimeUnit.HOURS)...
* </pre>
*
* @param tti default Time To Idle scalar value
* @param timeUnit default Time to Idle unit of time
* @return the builder instance for method chaining.
*/
CacheManagerBuilder withDefaultTimeToIdle(long tti, TimeUnit timeUnit);

/**
* Adds configuration settings for a specific Cache region managed by the {@link #build() built}
* {@code CacheManager}, like the region's Time to Live and Time to Idle.
*
* @param builder the CacheConfigurationBuilder instance that will be used to a cache's configuration.
* @return this instance for method chaining.
*/
CacheManagerBuilder withCache(CacheConfigurationBuilder builder);

/**
* Returns a new {@link CacheManager} instance reflecting Builder's current configuration.
*
* @return a new {@link CacheManager} instance reflecting Builder's current configuration.
*/
CacheManager build();
}
Loading

0 comments on commit 44bed7a

Please sign in to comment.