[java-oidc-common] branch main updated: Improve cache builders/factories

Phil Smart philip.smart at jisc.ac.uk
Fri Oct 29 13:04:50 UTC 2021


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch main
in repository java-oidc-common.

View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=99885d8024524c9274f994d970f765b79132a662

The following commit(s) were added to refs/heads/main by this push:
     new 99885d8  Improve cache builders/factories
99885d8 is described below

commit 99885d8024524c9274f994d970f765b79132a662
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Oct 29 14:04:47 2021 +0100

    Improve cache builders/factories
    
     - Improves XML configuration. Although runtime injection is obviously
    not going to be type safe.
     - Remove redundant factories
     - Add JSON map resolvers
---
 .../oidc/metadata/JSONMetadataResolver.java        |  38 ++
 .../metadata/cache/impl/AbstractMetadataCache.java |   3 +-
 .../cache/impl/BatchMetadataCacheBuilder.java      |  82 +++
 .../cache/impl/BatchMetadataCacheBuilderSpec.java  | 117 +++++
 .../cache/impl/DefaultJSONMapParsingStrategy.java  |   6 +-
 .../cache/impl/DynamicMetadataCacheBuilder.java    |  61 +++
 .../impl/DynamicMetadataCacheBuilderSpec.java      | 147 ++++++
 .../impl/MapBasedMetadataCacheFactoryBean.java     |  79 ---
 .../metadata/cache/impl/MetadataCacheBuilder.java  | 553 ---------------------
 .../cache/impl/MetadataCacheBuilderSpec.java       | 256 ++++++++++
 .../impl/OIDCProviderMetadataCacheFactoryBean.java |  83 ----
 ...aResolver.java => JSONMapMetadataResolver.java} |  15 +-
 .../impl/OIDCProviderMetadataResolver.java         |   2 +-
 .../cache/impl/BatchMetadataCacheBuilderTest.java  |  75 +++
 .../impl/DynamicMetadataCacheBuilderTest.java      |  76 +++
 15 files changed, 867 insertions(+), 726 deletions(-)

diff --git a/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/JSONMetadataResolver.java b/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/JSONMetadataResolver.java
new file mode 100644
index 0000000..b7329a0
--- /dev/null
+++ b/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/JSONMetadataResolver.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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 net.shibboleth.oidc.metadata;
+
+import java.util.Map;
+
+import net.shibboleth.oidc.metadata.criterion.IssuerIDCriterion;
+import net.shibboleth.utilities.java.support.component.IdentifiedComponent;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.Resolver;
+
+/**
+ * A resolver that is capable of resolving {@link Map} instances which meet certain supplied criteria.
+ * <p>
+ * At a minimum, a {@link JSONMetadataResolver} implementation MUST support the following criteria:</p>
+ * <ul>
+ * <li>{@link IssuerIDCriterion}</li>
+ * </ul>
+ */
+//TODO do we need this interface if we already have a concrete type?
+public interface JSONMetadataResolver extends Resolver<Map<String, Object>, CriteriaSet>, IdentifiedComponent {
+
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/AbstractMetadataCache.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/AbstractMetadataCache.java
index fc6b8cc..3329283 100644
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/AbstractMetadataCache.java
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/AbstractMetadataCache.java
@@ -50,6 +50,7 @@ import net.shibboleth.utilities.java.support.component.AbstractIdentifiableIniti
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
 import net.shibboleth.utilities.java.support.primitive.TimerSupport;
 import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
 
@@ -354,7 +355,7 @@ public abstract class AbstractMetadataCache<IdentifierType, MetadataType>
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
 
         if (factor <= 0 || factor >= 1) {
-            throw new IllegalArgumentException("Refresh delay factor must be a number between 0.0 and 1.0, exclusive");
+            throw new ConstraintViolationException("Refresh delay factor must be a number between 0.0 and 1.0, exclusive");
         }
 
         refreshDelayFactor = factor;
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/BatchMetadataCacheBuilder.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/BatchMetadataCacheBuilder.java
new file mode 100644
index 0000000..61e444f
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/BatchMetadataCacheBuilder.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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 net.shibboleth.oidc.metadata.cache.impl;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidc.metadata.impl.DefaultBatchBackingStore;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+
+/**
+ * Build a fully initialized and safely published batch metadata cache for use. Each cache is built from its own
+ * specification.
+ * 
+ * <p>Spring's XML injection can not enforce type safety here, so an incorrect specification will
+ * not be picked up until it is used.</p> 
+ * 
+ */
+public final class BatchMetadataCacheBuilder  {
+    
+    /** Private constructor.*/
+    private BatchMetadataCacheBuilder() {
+        
+    }
+
+    /**
+     * A static builder for generating a batch metadata cache from a given specification.
+     *
+     * @param <IdentifierType> The identifier type 
+     * @param <MetadataType> The metadata type>
+     */
+    public static class Builder<IdentifierType, MetadataType> {
+        
+    
+        /**
+         * Build the metadata cache from the given specification.
+         * 
+         * @param spec the metadata cache specification.
+         * 
+         * @return the batch metadata cache.
+         * 
+         * @throws ComponentInitializationException on error.
+         */
+        public BatchMetadataCache<IdentifierType, MetadataType> build(
+                @Nonnull final BatchMetadataCacheBuilderSpec<IdentifierType, MetadataType> spec) 
+                        throws ComponentInitializationException {
+    
+           final BatchMetadataCache<IdentifierType, MetadataType> cache = 
+                    new BatchMetadataCache<>(
+                    new DefaultBatchBackingStore<>(), spec.getLoadingStrategy(), spec.getParsingStrategy());
+            cache.setMinCacheDuration(spec.getMinCacheDuration());
+           // cache.setMaxCacheDuration(getMaxCacheDuration());
+            cache.setMinRefreshDelay(spec.getMinRefreshDelay());
+            cache.setMaxRefreshDelay(spec.getMaxRefreshDelay());
+            cache.setRefreshDelayFactor(spec.getRefreshDelayFactor());
+            cache.setMetadataExpirationTimeStrategy(spec.getMetadataExpirationTimeStrategy());
+            cache.setIdentifierExtractionStrategy(spec.getIdentifierExtractionStrategy());
+            cache.setCriteriaToIdentifierStrategy(spec.getCriteriaToIdentifierStrategy());
+            cache.setMetadataFilterStrategy(spec.getMetadataFilterStrategy());
+            cache.setMetadataBeforeRemovalHook(spec.getMetadataBeforeRemovalHook());
+            cache.setId("BatchMetadataCache");
+            cache.initialize();
+            return cache;
+        }
+    }
+    
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/BatchMetadataCacheBuilderSpec.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/BatchMetadataCacheBuilderSpec.java
new file mode 100644
index 0000000..0bf97c2
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/BatchMetadataCacheBuilderSpec.java
@@ -0,0 +1,117 @@
+package net.shibboleth.oidc.metadata.cache.impl;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidc.metadata.cache.CacheLoadingContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+public class BatchMetadataCacheBuilderSpec<IdentifierType, MetadataType> 
+                        extends MetadataCacheBuilderSpec<IdentifierType, MetadataType> {
+    
+    /** 
+     * How to parse the loaded metadata from the loadingStrategy into a usable metadatatype.
+     * Applicable for {@link BatchMetadataCache} types. 
+     */
+    @Nullable private Function<byte[], List<MetadataType>> parsingStrategy;
+    
+    /** The function to use to load metadata. Applicable for {@link BatchMetadataCache} types.*/
+    @Nullable private Function<CacheLoadingContext, byte[]> loadingStrategy;
+    
+    /**
+     * Refresh interval used when metadata does not contain any validUntil or cacheDuration information. 
+     * Default value: 4 hours
+     */
+    @Nonnull @Positive private Duration maxRefreshDelay;
+
+    /** Floor, in milliseconds, for the refresh interval. Default value: 5 minutes */
+    @Nonnull @Positive private Duration minRefreshDelay;
+    
+    /** Constructor.*/
+    public BatchMetadataCacheBuilderSpec() {
+        maxRefreshDelay = Duration.ofHours(4);
+        minRefreshDelay = Duration.ofMinutes(5);       
+    }
+    
+    /**
+     * Get the min delay to wait before refreshing metadata.
+     * 
+     * @return the min delay.
+     */
+    @Nonnull protected Duration getMinRefreshDelay() {
+        return minRefreshDelay;
+    }
+    
+    /**
+     * Get the max delay to wait before refreshing metadata.
+     * 
+     * @return the max delay.
+     */
+    @Nonnull protected Duration getMaxRefreshDelay() {
+        return maxRefreshDelay;
+    }
+    
+
+    
+    /**
+     * Sets the minimum amount of time between refreshes.
+     * 
+     * @param delay minimum amount of time between refreshes
+     */
+    public void setMinRefreshDelay(@Positive @Nonnull final Duration delay) {
+        Constraint.isFalse(delay == null || delay.isNegative(), "Minimum refresh delay must be greater than 0");
+        minRefreshDelay = delay;
+    }
+    
+    /**
+     * Sets the maximum amount of time between refresh intervals.
+     * 
+     * @param delay maximum amount of time, in milliseconds, between refresh intervals
+     */
+    public void setMaxRefreshDelay(@Positive @Nonnull final Duration delay) {        
+        Constraint.isFalse(delay == null || delay.isNegative(), "Maximum refresh delay must be greater than 0");
+        maxRefreshDelay = delay;
+    }    
+    
+    /**
+     * Set he raw batch metadata to metadata type parsing strategy.
+     * 
+     * @param strategy the parsing strategy.
+     */
+    public void setParsingStrategy(@Nonnull final Function<byte[], List<MetadataType>> strategy) {
+        parsingStrategy = Constraint.isNotNull(strategy, "Parsing strategy can not be null");
+    }
+    
+    /**
+     * Get the raw batch metadata to metadata type parsing strategy.
+     * 
+     * @return the parsing strategy.
+     */
+    @Nullable protected Function<byte[], List<MetadataType>> getParsingStrategy() {
+        return parsingStrategy;
+    }
+    
+    /**
+     * Set the batch metadata loading strategy, applies to the  {@link BatchMetadataCache} type.
+     * 
+     * @param strategy the strategy to set.
+     */
+    public void setLoadingStrategy(@Nonnull final Function<CacheLoadingContext, byte[]> strategy) {
+        loadingStrategy =  Constraint.isNotNull(strategy,"Batch metadata loading strategy can not be null");
+    }
+    
+    /**
+     * Get the batch metadata loading strategy. 
+     * 
+     * @return the strategy.
+     */
+    @Nullable protected Function<CacheLoadingContext, byte[]> getLoadingStrategy() {
+        return loadingStrategy;
+    }
+
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/DefaultJSONMapParsingStrategy.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/DefaultJSONMapParsingStrategy.java
index 0b96004..dffef6a 100644
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/DefaultJSONMapParsingStrategy.java
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/DefaultJSONMapParsingStrategy.java
@@ -52,6 +52,10 @@ public class DefaultJSONMapParsingStrategy implements Function<byte[], List<Map<
     public DefaultJSONMapParsingStrategy(@Nonnull final ObjectMapper mapper) {
         objectMapper = Constraint.isNotNull(mapper, "Object mapper can not be null");
     }
+    
+    public DefaultJSONMapParsingStrategy() {
+        this(new ObjectMapper());
+    }
 
     @Override
     public List<Map<String, Object>> apply(byte[] rawMetadata) {
@@ -64,7 +68,7 @@ public class DefaultJSONMapParsingStrategy implements Function<byte[], List<Map<
             }
             return Collections.emptyList();
         } catch (final JsonProcessingException e) {
-            log.error("Could not parse input raw metadata to a map");
+            log.error("Could not parse input raw metadata to a map", e);
             return Collections.emptyList();
         }
     }
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/DynamicMetadataCacheBuilder.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/DynamicMetadataCacheBuilder.java
new file mode 100644
index 0000000..075bb98
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/DynamicMetadataCacheBuilder.java
@@ -0,0 +1,61 @@
+
+package net.shibboleth.oidc.metadata.cache.impl;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidc.metadata.impl.DefaultDynamicBackingStore;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/**
+ * Build a fully initialized and safely published dynamic metadata cache for use. Each cache is built from its own
+ * specification.
+ * 
+ * <p>Spring's XML injection can not enforce type safety here, so an incorrect specification will
+ * not be picked up until it is used.</p> 
+ * 
+ * @param <IdentifierType> The identifier type 
+ * @param <MetadataType> The metadata type
+ */
+public final class DynamicMetadataCacheBuilder {
+    
+    /** Private constructor.*/
+    private DynamicMetadataCacheBuilder() {
+        
+    }
+
+    /**
+     * A static builder for generating a dynamic metadata cache from a given specification.
+     *
+     * @param <IdentifierType> The identifier type 
+     * @param <MetadataType> The metadata type>
+     */
+    public static class Builder<IdentifierType, MetadataType> {
+
+        public DynamicMetadataCache<IdentifierType, MetadataType>
+                build(@Nonnull final DynamicMetadataCacheBuilderSpec<IdentifierType, MetadataType> spec) 
+                        throws ComponentInitializationException {
+
+            final DynamicMetadataCache<IdentifierType, MetadataType> cache = new DynamicMetadataCache<>(
+                    new DefaultDynamicBackingStore<>(spec.getMaxCacheDuration()), spec.getFetchStrategy());
+            cache.setMinCacheDuration(spec.getMinCacheDuration());
+            // FIXME what did we do with this.
+            // cache.setMaxCacheDuration(getMaxCacheDuration());
+            cache.setRefreshDelayFactor(spec.getRefreshDelayFactor());
+            cache.setMaxIdleEntityData(spec.getMaxIdleEntityData());
+            cache.setMetadataExpirationTimeStrategy(spec.getMetadataExpirationTimeStrategy());
+            cache.setIdentifierExtractionStrategy(spec.getIdentifierExtractionStrategy());
+            cache.setCriteriaToIdentifierStrategy(spec.getCriteriaToIdentifierStrategy());
+            cache.setCleanupTaskInterval(spec.getCleanupTaskInterval());
+            cache.setRemoveIdleEntityData(spec.isRemoveIdleEntityData());
+            cache.setInitialCleanupTaskDelay(spec.getInitialCleanupTaskDelay());
+            cache.setMetadataFilterStrategy(spec.getMetadataFilterStrategy());
+            cache.setMetadataBeforeRemovalHook(spec.getMetadataBeforeRemovalHook());
+            cache.setId("DynamicMetadataCache");
+            cache.initialize();
+            return cache;
+        }
+
+    }
+
+
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/DynamicMetadataCacheBuilderSpec.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/DynamicMetadataCacheBuilderSpec.java
new file mode 100644
index 0000000..186643b
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/DynamicMetadataCacheBuilderSpec.java
@@ -0,0 +1,147 @@
+package net.shibboleth.oidc.metadata.cache.impl;
+
+import java.time.Duration;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+public class DynamicMetadataCacheBuilderSpec <IdentifierType, MetadataType> 
+                        extends MetadataCacheBuilderSpec<IdentifierType, MetadataType> {
+    
+    
+    /** The function to use to fetch metadata if either none exists, or the existing is stale.*/
+    @Nullable private Function<CriteriaSet, MetadataType> fetchStrategy;
+    
+    /** The maximum idle time for which the cache will keep data for before it is removed. */
+    @Nullable private Duration maxIdleEntityData;
+    
+    /** The interval at which the cleanup task should run. */
+    @Nonnull private Duration cleanupTaskInterval;
+    
+    /** Flag indicating whether idle entity data should be removed. */
+    private boolean removeIdleEntityData;
+    
+    /** The initial cleanup task delay.*/
+    @Nonnull private Duration initialCleanupTaskDelay;
+    
+    /** Constructor. */
+    protected DynamicMetadataCacheBuilderSpec() {
+        maxIdleEntityData = Duration.ofHours(8);
+        cleanupTaskInterval = Duration.ofMinutes(30);
+        initialCleanupTaskDelay = Duration.ofMinutes(1);
+        removeIdleEntityData = true;
+    }
+    
+    /**
+     * Set the flag indicating whether idle entity data should be removed. 
+     * 
+     * @param flag true if idle entity data should be removed, false otherwise
+     */
+    public void setRemoveIdleEntityData(final boolean flag) {       
+        removeIdleEntityData = flag;
+    }
+    
+    /**
+     * Should idle metadata be removed?
+     * 
+     * @return if idle metadata should be resolved.
+     */
+    protected boolean isRemoveIdleEntityData() {
+        return removeIdleEntityData;
+    }
+    
+    /**
+     * Get the interval at which the cleanup task should run.
+     * 
+     * <p>Defaults to: 30 minutes.</p>
+     * 
+     * @return return the interval
+     */
+    @Nonnull protected Duration getCleanupTaskInterval() {
+        return cleanupTaskInterval;
+    }
+
+    /**
+     * Set the interval at which the cleanup task should run.
+     * 
+     * <p>Defaults to: 30 minutes.</p>
+     * 
+     * @param interval the interval to set
+     */
+    public void setCleanupTaskInterval(@Nonnull final Duration interval) {        
+        Constraint.isNotNull(interval, "Cleanup task interval may not be null");
+        Constraint.isFalse(interval.isNegative() || interval.isZero(), "Cleanup task interval must be positive");
+        
+        cleanupTaskInterval = interval;
+    }
+    
+    /**
+     * Set the metadata fetching strategy.
+     * 
+     * @param strategy the strategy to use.
+     */
+    public void setFetchStrategy(@Nonnull final Function<CriteriaSet, MetadataType> strategy) {
+        fetchStrategy = Constraint.isNotNull(strategy, "Metadata fetch strategy can not be null");
+    }
+    
+    /**
+     * Get the metadata fetching strategy.
+     * 
+     * @return the fetching strategy.
+     */
+    @Nullable protected Function<CriteriaSet, MetadataType> getFetchStrategy() {
+        return fetchStrategy;
+    }
+    
+    /**
+     * Get the maximum idle time for which the resolver will keep data for before it is removed.
+     * 
+     * <p>Defaults to: 8 hours.</p>
+     * 
+     * @return return the maximum idle time
+     */
+    @Nonnull protected Duration getMaxIdleEntityData() {
+        return maxIdleEntityData;
+    }
+
+    /**
+     * Set the maximum idle time for which the resolver will keep data for before it is removed.
+     * 
+     * <p>Defaults to: 8 hours.</p>
+     * 
+     * @param max the maximum entity data idle time
+     */
+    public void setMaxIdleEntityData(@Nonnull final Duration max) {
+        Constraint.isNotNull(max, "Max idle time cannot be null");
+        Constraint.isFalse(max.isNegative(), "Max idle time cannot be negative");
+
+        maxIdleEntityData = max;
+    }
+    
+    
+    /**
+     * Get the initial cleanup task delay.
+     * 
+     * @return Returns the initialCleanupTaskDelay.
+     */
+    @Nonnull protected Duration getInitialCleanupTaskDelay() {
+        return initialCleanupTaskDelay;
+    }
+
+    /**
+     * Set the initial cleanup task delay.
+     * 
+     * @param delay The initialCleanupTaskDelay to set.
+     */
+    public void setInitialCleanupTaskDelay(@Nonnull final Duration delay) {
+        Constraint.isNotNull(delay, "Cleanup task delay can not be null");
+        Constraint.isFalse(delay.isNegative() || delay.isZero(), "Cleanup task delay must be positive");
+        initialCleanupTaskDelay = delay;
+        
+    }
+
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MapBasedMetadataCacheFactoryBean.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MapBasedMetadataCacheFactoryBean.java
deleted file mode 100644
index 111d3bd..0000000
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MapBasedMetadataCacheFactoryBean.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package net.shibboleth.oidc.metadata.cache.impl;
-
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import net.shibboleth.oidc.metadata.cache.MetadataCache;
-import net.shibboleth.oidc.metadata.impl.DefaultBatchBackingStore;
-import net.shibboleth.oidc.metadata.impl.DefaultDynamicBackingStore;
-
-/**
- * Factory bean to create a map based metadata cache. Either a {@link DynamicMetadataCache} 
- * or {@link BatchMetadataCache} is created depending on the cache properties configured.
- */
-public class MapBasedMetadataCacheFactoryBean extends MetadataCacheBuilder<String, Map<String, Object>> {
-    
-    
-    private final AtomicInteger cacheID;
-    
-    /** Constructor.*/
-    public MapBasedMetadataCacheFactoryBean() {
-        cacheID = new AtomicInteger(1);
-    }
-
-    @SuppressWarnings("rawtypes")
-    @Override
-    public Class<MetadataCache> getObjectType() {
-        return MetadataCache.class;
-    }
-    
-    @Override
-    protected MetadataCache<Map<String, Object>> createInstance() throws Exception {
-        
-        if (getCacheOperationMode() == CacheOperationMode.DYNAMIC) {
-            final DynamicMetadataCache<String, Map<String, Object>> cache = new DynamicMetadataCache<>(
-                    new DefaultDynamicBackingStore<>(getMaxCacheDuration()), getFetchStrategy());
-            cache.setMinCacheDuration(getMinCacheDuration());
-            //FIXME what did we do with this.
-           // cache.setMaxCacheDuration(getMaxCacheDuration());
-            cache.setRefreshDelayFactor(getRefreshDelayFactor());
-            cache.setMaxIdleEntityData(getMaxIdleEntityData());
-            cache.setMetadataExpirationTimeStrategy(getMetadataExpirationTimeStrategy());
-            cache.setIdentifierExtractionStrategy(getIdentifierExtractionStrategy());
-            cache.setCriteriaToIdentifierStrategy(getCriteriaToIdentifierStrategy());
-            cache.setCleanupTaskInterval(getCleanupTaskInterval());
-            cache.setRemoveIdleEntityData(isRemoveIdleEntityData());
-            cache.setInitialCleanupTaskDelay(getInitialCleanupTaskDelay());
-            cache.setMetadataFilterStrategy(getMetadataFilterStrategy());
-            cache.setMetadataBeforeRemovalHook(getMetadataBeforeRemovalHook());
-            cache.setId("MapBasedDynamicMetadataCache"+cacheID.getAndIncrement());
-            cache.initialize();
-            return cache;
-        } else if (getCacheOperationMode() == CacheOperationMode.BATCH) {
-            //TODO why have the backing stores as interfaces if you are setting the concrete type here? inject somehow?
-            final BatchMetadataCache<String, Map<String, Object>> cache = 
-                    new BatchMetadataCache<>(
-                    new DefaultBatchBackingStore<>(), getLoadingStrategy(), getParsingStrategy());
-            cache.setMinCacheDuration(getMinCacheDuration());
-           // cache.setMaxCacheDuration(getMaxCacheDuration());
-            cache.setMinRefreshDelay(getMinRefreshDelay());
-            cache.setMaxRefreshDelay(getMaxRefreshDelay());
-            cache.setRefreshDelayFactor(getRefreshDelayFactor());
-            cache.setMetadataExpirationTimeStrategy(getMetadataExpirationTimeStrategy());
-            cache.setIdentifierExtractionStrategy(getIdentifierExtractionStrategy());
-            cache.setCriteriaToIdentifierStrategy(getCriteriaToIdentifierStrategy());
-            cache.setMetadataFilterStrategy(getMetadataFilterStrategy());
-            cache.setMetadataBeforeRemovalHook(getMetadataBeforeRemovalHook());
-            cache.setId("MapBasedBatchMetadataCache"+cacheID.getAndIncrement());
-            cache.initialize();
-            return cache;
-        } else {
-            //should never get here.
-            throw new Exception("Cache mode of operation not supported");
-        }
-        
-    }
-   
-
-
-}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MetadataCacheBuilder.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MetadataCacheBuilder.java
deleted file mode 100644
index f0d44d7..0000000
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MetadataCacheBuilder.java
+++ /dev/null
@@ -1,553 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You 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 net.shibboleth.oidc.metadata.cache.impl;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.List;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.springframework.beans.factory.FactoryBeanNotInitializedException;
-import org.springframework.beans.factory.config.AbstractFactoryBean;
-
-import net.shibboleth.oidc.metadata.cache.CacheLoadingContext;
-import net.shibboleth.oidc.metadata.cache.MetadataCache;
-import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
-import net.shibboleth.utilities.java.support.component.DestructableComponent;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
-
-/** 
- * Base metadata cache builder instance. Holds values common to all metadata cache implementations.
- * 
- * @param <T> the metadata identifier/key
- * @param <U> the metadata type. 
- */
-public abstract class MetadataCacheBuilder<T,U> extends AbstractFactoryBean<MetadataCache<U>> {
-    
-    
-    /** Which mode of operation to support.*/
-    public enum CacheOperationMode {
-        
-        /** 
-         * A batch mode of operation where caches are completely reloaded
-         * on refresh using refresh-ahead semantics.
-         * Satisfied by constructing a {@link BatchMetadataCache}.
-         */
-        BATCH,
-        
-        /**
-         * A dynamic mode of operation where individual entries are updated
-         * when stale or not found using a read-through semantic.
-         * Satisfied by constructing a {@link DynamicMetadataCache}.
-         */
-        DYNAMIC,
-    }
-    
-    /** Maximum cache duration. */
-    @Nonnull private Duration maxCacheDuration;
-    
-    /** Minimum cache duration. */
-    @Nonnull private Duration minCacheDuration;
-    
-    /**
-     * Refresh interval used when metadata does not contain any validUntil or cacheDuration information. Default value:
-     * 4 hours
-     */
-    @Nonnull @Positive private Duration maxRefreshDelay;
-
-    /** Floor, in milliseconds, for the refresh interval. Default value: 5 minutes */
-    @Nonnull @Positive private Duration minRefreshDelay;
-    
-    /** Factor used to compute when the next refresh interval will occur. Default value: 0.75 */
-    @Positive private Float refreshDelayFactor;
-    
-    /** The maximum idle time for which the resolver will keep data for a given entityID, 
-     * before it is removed. */
-    @Nullable private Duration maxIdleEntityData;
-    
-    /** The interval at which the cleanup task should run. */
-    @Nonnull private Duration cleanupTaskInterval;
-    
-    /** The initial cleanup task delay.*/
-    @Nonnull private Duration initialCleanupTaskDelay;
-    
-    /** Strategy used to extract an identifier from the given metadata.*/
-    @Nullable private Function<U, T> identifierExtractionStrategy;
-    
-    /** Strategy used to compute an expiration time. */
-    @Nullable private BiFunction<U, Instant, Instant> metadataExpirationTimeStrategy;
-    
-    /** Map criteria to identifiers to use as keys to the backing store.*/
-    @Nullable private Function<CriteriaSet, T> criteriaToIdentifierStrategy;
-    
-    /** A strategy to filter metadata. */
-    @Nonnull private BiFunction<U, MetadataFilterContext , U> metadataFilterStrategy;
-    
-    /** 
-     * A hook that is executed just before a cache entry will been removed/invalidated/evicted.
-     * The metadata list could be {@literal null}, the identifier is never {@literal null}.
-     */
-    @Nullable private BiConsumer<List<U>, T> metadataBeforeRemovalHook;
-    
-    /** Flag indicating whether idle entity data should be removed. */
-    private boolean removeIdleEntityData;
-    
-    /** The function to use to fetch metadata if either none exists, or the existing is stale.*/
-    @Nullable private Function<CriteriaSet, U> fetchStrategy;
-    
-    /** 
-     * How to parse the loaded metadata from the loadingStrategy into a usable metadatatype.
-     * Applicable for {@link BatchMetadataCache} types. 
-     */
-    @Nullable private Function<byte[], List<U>> parsingStrategy;
-    
-    /** The function to use to load metadata. Applicable for {@link BatchMetadataCache} types.*/
-    @Nullable private Function<CacheLoadingContext, byte[]> loadingStrategy;
-    
-    /** Which cache mode of operation to use.*/
-    @Nonnull private CacheOperationMode cacheOperationMode;
-    
-    
-    
-    /** Constructor.*/
-    protected MetadataCacheBuilder() {
-        // defaults
-        maxCacheDuration = Duration.ofHours(8);
-        minCacheDuration = Duration.ofMinutes(10);
-        maxIdleEntityData = Duration.ofHours(8);
-        refreshDelayFactor = 0.75f;
-        cleanupTaskInterval = Duration.ofMinutes(30);
-        initialCleanupTaskDelay = Duration.ofMinutes(1);
-        maxRefreshDelay = Duration.ofHours(4);
-        minRefreshDelay = Duration.ofMinutes(5);
-        removeIdleEntityData = true;
-        // create a default direct in/out filter
-        metadataFilterStrategy = (metadata, context) -> metadata;        
-        // create batch mode by default
-        cacheOperationMode = CacheOperationMode.BATCH;
-    }
-    
-    /**
-     * {@inheritDoc}
-     * <p>Only supports prototype metadata instances, each resolver must have its own cache.</p>
-     */
-    @Override
-    public void afterPropertiesSet() throws Exception {        
-        if (isSingleton()) {
-            throw new 
-                FactoryBeanNotInitializedException("Only prototype metadata cache instances are supported");
-        }
-        super.afterPropertiesSet();
-    }
-    
-    /**
-     * Set the cache operation mode.
-     * 
-     * @param mode the cache operation mode.
-     */
-    public void setCacheOperationMode(@Nonnull final CacheOperationMode mode) {
-        cacheOperationMode = Constraint.isNotNull(mode, "Cache operation mode can not be null");
-    }
-    
-    /**
-     * Get the cache operation mode.
-     * 
-     * @return the cache operation mode.
-     */
-    @Nonnull protected CacheOperationMode getCacheOperationMode() {
-        return cacheOperationMode;
-    }
-    
-    /**
-     * Get the min delay to wait before refreshing metadata.
-     * 
-     * @return the min delay.
-     */
-    @Nonnull protected Duration getMinRefreshDelay() {
-        return minRefreshDelay;
-    }
-    
-    /**
-     * Get the max delay to wait before refreshing metadata.
-     * 
-     * @return the max delay.
-     */
-    @Nonnull protected Duration getMaxRefreshDelay() {
-        return maxRefreshDelay;
-    }
-    
-    /**
-     * Set he raw batch metadata to metadata type parsing strategy.
-     * 
-     * @param strategy the parsing strategy.
-     */
-    public void setParsingStrategy(@Nonnull final Function<byte[], List<U>> strategy) {
-        parsingStrategy = Constraint.isNotNull(strategy, "Parsing strategy can not be null");
-    }
-    
-    /**
-     * Get the raw batch metadata to metadata type parsing strategy.
-     * 
-     * @return the parsing strategy.
-     */
-    @Nullable protected Function<byte[], List<U>> getParsingStrategy() {
-        return parsingStrategy;
-    }
-    
-    /**
-     * Set the batch metadata loading strategy, applies to the  {@link BatchMetadataCache} type.
-     * 
-     * @param strategy the strategy to set.
-     */
-    public void setLoadingStrategy(@Nonnull final Function<CacheLoadingContext, byte[]> strategy) {
-        loadingStrategy =  Constraint.isNotNull(strategy,"Batch metadata loading strategy can not be null");
-    }
-    
-    /**
-     * Get the batch metadata loading strategy. 
-     * 
-     * @return the strategy.
-     */
-    @Nullable protected Function<CacheLoadingContext, byte[]> getLoadingStrategy() {
-        return loadingStrategy;
-    }
-    
-    /**
-     * Sets the minimum amount of time between refreshes.
-     * 
-     * @param delay minimum amount of time between refreshes
-     */
-    public void setMinRefreshDelay(@Positive @Nonnull final Duration delay) {
-        Constraint.isFalse(delay == null || delay.isNegative(), "Minimum refresh delay must be greater than 0");
-        minRefreshDelay = delay;
-    }
-    
-    /**
-     * Sets the maximum amount of time between refresh intervals.
-     * 
-     * @param delay maximum amount of time, in milliseconds, between refresh intervals
-     */
-    public void setMaxRefreshDelay(@Positive @Nonnull final Duration delay) {        
-        Constraint.isFalse(delay == null || delay.isNegative(), "Maximum refresh delay must be greater than 0");
-        maxRefreshDelay = delay;
-    }
-    
-    /**
-     * Set the metadata fetching strategy.
-     * 
-     * @param strategy the strategy to use.
-     */
-    public void setFetchStrategy(@Nonnull final Function<CriteriaSet, U> strategy) {
-        fetchStrategy = Constraint.isNotNull(strategy, "Metadata fetch strategy can not be null");
-    }
-    
-    /**
-     * Get the metadata fetching strategy.
-     * 
-     * @return the fetching strategy.
-     */
-    @Nullable protected Function<CriteriaSet, U> getFetchStrategy() {
-        return fetchStrategy;
-    }
-    
-    /**
-     * {@inheritDoc}
-     * 
-     * Call destroy if the cache implementation supports it.
-     */
-    @Override protected void destroyInstance(
-            final @Nullable MetadataCache<U> instance) throws Exception {
-        if (instance instanceof DestructableComponent) {
-            ((DestructableComponent)instance).destroy();
-        }
-    }
-    
-    /**
-     * Set a hook to run before a metadata cache entry is removed from the cache.
-     * <p>The hook is required to gracefully handle null metadata lists.</p>
-     * 
-     * @param hook the hook to run.
-     */
-    public void setMetadataBeforeRemovalHook(@Nullable final BiConsumer<List<U>, T> hook) {        
-        metadataBeforeRemovalHook = hook;
-    }
-    
-    /**
-     * Get the metadata before removal hook. Could be {@literal null}.
-     * 
-     * @return the hook.
-     */
-    @Nullable public BiConsumer<List<U>, T> getMetadataBeforeRemovalHook() {
-        return metadataBeforeRemovalHook;
-    }
-    
-    /**
-     * Set the metadata filtering strategy.
-     * 
-     * <p>Defaults to a no-op strategy.</p>
-     * 
-     * @param strategy the metadata filtering strategy.
-     */
-    public void setMetadataFilterStrategy(@Nonnull final BiFunction<U, MetadataFilterContext, U> strategy) {  
-        metadataFilterStrategy = Constraint.isNotNull(strategy,"Metadata filtering strategy can not be null");
-    }
-    
-    /**
-     * Get the metadata filter strategy.
-     * 
-     * @return the metadata filtering strategy
-     */
-    @Nonnull public BiFunction<U, MetadataFilterContext, U> getMetadataFilterStrategy() {
-        return metadataFilterStrategy;
-    }
-    
-    /**
-     * Get the initial cleanup task delay.
-     * 
-     * @return Returns the initialCleanupTaskDelay.
-     */
-    @Nonnull protected Duration getInitialCleanupTaskDelay() {
-        return initialCleanupTaskDelay;
-    }
-
-    /**
-     * Set the initial cleanup task delay.
-     * 
-     * @param delay The initialCleanupTaskDelay to set.
-     */
-    public void setInitialCleanupTaskDelay(@Nonnull final Duration delay) {
-        Constraint.isNotNull(delay, "Cleanup task delay can not be null");
-        Constraint.isFalse(delay.isNegative() || delay.isZero(), "Cleanup task delay must be positive");
-        initialCleanupTaskDelay = delay;
-        
-    }
-    
-    /**
-     * Set the flag indicating whether idle entity data should be removed. 
-     * 
-     * @param flag true if idle entity data should be removed, false otherwise
-     */
-    public void setRemoveIdleEntityData(final boolean flag) {       
-        removeIdleEntityData = flag;
-    }
-    
-    /**
-     * Should idle metadata be removed?
-     * 
-     * @return if idle metadata should be resolved.
-     */
-    protected boolean isRemoveIdleEntityData() {
-        return removeIdleEntityData;
-    }
-    
-    /**
-     * Set the identifier extraction strategy.
-     * 
-     * @param strategy the strategy to set.
-     */
-    public void setIdentifierExtractionStrategy(@Nonnull final Function<U, T> strategy) {        
-        identifierExtractionStrategy = Constraint.isNotNull(strategy, "Strategy can not be null");
-    }
-    
-
-    /** 
-     * Get the identifier extraction strategy.
-     * 
-     * @return strategy the strategy.
-     */
-    @Nullable protected Function<U, T> getIdentifierExtractionStrategy() {
-        return identifierExtractionStrategy;
-    }
-    
-    /**
-     * Set the metadata expiration time strategy.
-     * 
-     * @param strategy the strategy.
-     */
-    public void setMetadataExpirationTimeStrategy(
-            @Nonnull final BiFunction<U, Instant, Instant> strategy) {        
-        metadataExpirationTimeStrategy = Constraint.isNotNull(strategy, "Strategy can not be null");
-    }
-    
-    /**
-     * Get the metadata expiration time strategy.
-     * 
-     * @return the strategy.
-     */
-    @Nullable protected BiFunction<U, Instant, Instant> getMetadataExpirationTimeStrategy() {
-        return metadataExpirationTimeStrategy;
-    }
-    
-    /**
-     * Set the criteria set to identifier lookup strategy.
-     * 
-     * @param strategy the strategy to set.
-     */
-    public void setCriteriaToIdentifierStrategy(@Nonnull final Function<CriteriaSet, T> strategy) {
-        criteriaToIdentifierStrategy =  
-                Constraint.isNotNull(strategy,"Criteria to identifier strategy can not be null");
-    }
-    
-    /**
-     * Get the criteria set to identifier lookup strategy.
-     * 
-     * @return strategy the strategy.
-     */
-    @Nullable protected Function<CriteriaSet, T> getCriteriaToIdentifierStrategy() {
-        return criteriaToIdentifierStrategy;
-    }
-    
-    /**
-     * Get the interval at which the cleanup task should run.
-     * 
-     * <p>Defaults to: 30 minutes.</p>
-     * 
-     * @return return the interval
-     */
-    @Nonnull protected Duration getCleanupTaskInterval() {
-        return cleanupTaskInterval;
-    }
-
-    /**
-     * Set the interval at which the cleanup task should run.
-     * 
-     * <p>Defaults to: 30 minutes.</p>
-     * 
-     * @param interval the interval to set
-     */
-    public void setCleanupTaskInterval(@Nonnull final Duration interval) {        
-        Constraint.isNotNull(interval, "Cleanup task interval may not be null");
-        Constraint.isFalse(interval.isNegative() || interval.isZero(), "Cleanup task interval must be positive");
-        
-        cleanupTaskInterval = interval;
-    }
-    
-    /**
-     * Get the maximum idle time for which the resolver will keep data for a given entityID, 
-     * before it is removed.
-     * 
-     * <p>Defaults to: 8 hours.</p>
-     * 
-     * @return return the maximum idle time
-     */
-    @Nonnull protected Duration getMaxIdleEntityData() {
-        return maxIdleEntityData;
-    }
-
-    /**
-     * Set the maximum idle time for which the resolver will keep data for a given entityID, 
-     * before it is removed.
-     * 
-     * <p>Defaults to: 8 hours.</p>
-     * 
-     * @param max the maximum entity data idle time
-     */
-    public void setMaxIdleEntityData(@Nonnull final Duration max) {
-        Constraint.isNotNull(max, "Max idle time cannot be null");
-        Constraint.isFalse(max.isNegative(), "Max idle time cannot be negative");
-
-        maxIdleEntityData = max;
-    }
-    
-    /**
-     *  Get the maximum cache duration for metadata.
-     *  
-     *  <p>Defaults to: 8 hours.</p>
-     *  
-     * @return the maximum cache duration
-     */
-    @Nonnull protected Duration getMaxCacheDuration() {
-        return maxCacheDuration;
-    }
-
-    /**
-     *  Set the maximum cache duration for metadata.
-     *  
-     *  <p>Defaults to: 8 hours.</p>
-     *  
-     * @param duration the maximum cache duration
-     */
-    public void setMaxCacheDuration(@Nonnull final Duration duration) {        
-        Constraint.isNotNull(duration, "Duration cannot be null");
-        Constraint.isFalse(duration.isNegative(), "Duration cannot be negative");
-        
-        maxCacheDuration = duration;
-    }
-    
-    /**
-     *  Get the minimum cache duration for metadata.
-     *  
-     *  <p>Defaults to: 10 minutes.</p>
-     *  
-     * @return the minimum cache duration
-     */
-    @Nonnull protected Duration getMinCacheDuration() {
-        return minCacheDuration;
-    }
-    
-
-    /**
-     *  Set the minimum cache duration for metadata.
-     *  
-     *  <p>Defaults to: 10 minutes.</p>
-     *  
-     * @param duration the minimum cache duration
-     */
-    public void setMinCacheDuration(@Nonnull final Duration duration) {
-        Constraint.isNotNull(duration, "Duration cannot be null");
-        Constraint.isFalse(duration.isNegative(), "Duration cannot be negative");
-        
-        minCacheDuration = duration;
-    }
-    
-    /**
-     * Gets the delay factor used to compute the next refresh time.
-     * 
-     * <p>Defaults to:  0.75.</p>
-     * 
-     * @return delay factor used to compute the next refresh time
-     */
-    @Nonnull protected Float getRefreshDelayFactor() {
-        return refreshDelayFactor;
-    }
-    
-
-    /**
-     * Sets the delay factor used to compute the next refresh time. The delay must be between 0.0 and 1.0, exclusive.
-     * 
-     * <p>Defaults to:  0.75.</p>
-     * 
-     * @param factor delay factor used to compute the next refresh time
-     */
-    public void setRefreshDelayFactor(@Nonnull final Float factor) {
-
-        if (factor <= 0 || factor >= 1) {
-            throw new IllegalArgumentException("Refresh delay factor must be a number between 0.0 and 1.0, exclusive");
-        }
-
-        refreshDelayFactor = factor;
-    }
-
-}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MetadataCacheBuilderSpec.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MetadataCacheBuilderSpec.java
new file mode 100644
index 0000000..93df6b2
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MetadataCacheBuilderSpec.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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 net.shibboleth.oidc.metadata.cache.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+public class MetadataCacheBuilderSpec<IdentifierType, MetadataType> {
+    
+    /** Maximum cache duration. */
+    @Nonnull private Duration maxCacheDuration;
+    
+    /** Minimum cache duration. */
+    @Nonnull private Duration minCacheDuration;
+        
+    /** Factor used to compute when the next refresh interval will occur. Default value: 0.75 */
+    @Positive private Float refreshDelayFactor;   
+          
+    /** Strategy used to extract an identifier from the given metadata.*/
+    @Nullable private Function<MetadataType, IdentifierType> identifierExtractionStrategy;
+    
+    /** Strategy used to compute an expiration time. */
+    @Nullable private BiFunction<MetadataType, Instant, Instant> metadataExpirationTimeStrategy;
+    
+    /** Map criteria to identifiers to use as keys to the backing store.*/
+    @Nullable private Function<CriteriaSet, IdentifierType> criteriaToIdentifierStrategy;
+    
+    /** A strategy to filter metadata. */
+    @Nonnull private BiFunction<MetadataType, MetadataFilterContext , MetadataType> metadataFilterStrategy;
+    
+    /** 
+     * A hook that is executed just before a cache entry will been removed/invalidated/evicted.
+     * The metadata list could be {@literal null}, the identifier is never {@literal null}.
+     */
+    @Nullable private BiConsumer<List<MetadataType>, IdentifierType> metadataBeforeRemovalHook;
+    
+    
+    /** Constructor.*/
+    protected MetadataCacheBuilderSpec() {
+        // defaults
+        maxCacheDuration = Duration.ofHours(8);
+        minCacheDuration = Duration.ofMinutes(10);        
+        refreshDelayFactor = 0.75f;         
+        // create a default direct in/out filter
+        metadataFilterStrategy = (metadata, context) -> metadata;        
+
+    }
+    
+   
+    /**
+     * Set a hook to run before a metadata cache entry is removed from the cache.
+     * <p>The hook is required to gracefully handle null metadata lists.</p>
+     * 
+     * @param hook the hook to run.
+     */
+    public void setMetadataBeforeRemovalHook(@Nullable final BiConsumer<List<MetadataType>, IdentifierType> hook) {        
+        metadataBeforeRemovalHook = hook;
+    }
+    
+    /**
+     * Get the metadata before removal hook. Could be {@literal null}.
+     * 
+     * @return the hook.
+     */
+    @Nullable public BiConsumer<List<MetadataType>, IdentifierType> getMetadataBeforeRemovalHook() {
+        return metadataBeforeRemovalHook;
+    }
+    
+    /**
+     * Set the metadata filtering strategy.
+     * 
+     * <p>Defaults to a no-op strategy.</p>
+     * 
+     * @param strategy the metadata filtering strategy.
+     */
+    public void setMetadataFilterStrategy(@Nonnull final BiFunction<MetadataType, MetadataFilterContext, MetadataType> strategy) {  
+        metadataFilterStrategy = Constraint.isNotNull(strategy,"Metadata filtering strategy can not be null");
+    }
+    
+    /**
+     * Get the metadata filter strategy.
+     * 
+     * @return the metadata filtering strategy
+     */
+    @Nonnull public BiFunction<MetadataType, MetadataFilterContext, MetadataType> getMetadataFilterStrategy() {
+        return metadataFilterStrategy;
+    }
+
+      
+    /**
+     * Set the identifier extraction strategy.
+     * 
+     * @param strategy the strategy to set.
+     */
+    public void setIdentifierExtractionStrategy(@Nonnull final Function<MetadataType, IdentifierType> strategy) {        
+        identifierExtractionStrategy = Constraint.isNotNull(strategy, "Strategy can not be null");
+    }
+    
+
+    /** 
+     * Get the identifier extraction strategy.
+     * 
+     * @return strategy the strategy.
+     */
+    @Nullable protected Function<MetadataType, IdentifierType> getIdentifierExtractionStrategy() {
+        return identifierExtractionStrategy;
+    }
+    
+    /**
+     * Set the metadata expiration time strategy.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setMetadataExpirationTimeStrategy(
+            @Nonnull final BiFunction<MetadataType, Instant, Instant> strategy) {        
+        metadataExpirationTimeStrategy = Constraint.isNotNull(strategy, "Strategy can not be null");
+    }
+    
+    /**
+     * Get the metadata expiration time strategy.
+     * 
+     * @return the strategy.
+     */
+    @Nullable protected BiFunction<MetadataType, Instant, Instant> getMetadataExpirationTimeStrategy() {
+        return metadataExpirationTimeStrategy;
+    }
+    
+    /**
+     * Set the criteria set to identifier lookup strategy.
+     * 
+     * @param strategy the strategy to set.
+     */
+    public void setCriteriaToIdentifierStrategy(@Nonnull final Function<CriteriaSet, IdentifierType> strategy) {
+        criteriaToIdentifierStrategy =  
+                Constraint.isNotNull(strategy,"Criteria to identifier strategy can not be null");
+    }
+    
+    /**
+     * Get the criteria set to identifier lookup strategy.
+     * 
+     * @return strategy the strategy.
+     */
+    @Nullable protected Function<CriteriaSet, IdentifierType> getCriteriaToIdentifierStrategy() {
+        return criteriaToIdentifierStrategy;
+    }
+    
+     
+    /**
+     *  Get the maximum cache duration for metadata.
+     *  
+     *  <p>Defaults to: 8 hours.</p>
+     *  
+     * @return the maximum cache duration
+     */
+    @Nonnull protected Duration getMaxCacheDuration() {
+        return maxCacheDuration;
+    }
+
+    /**
+     *  Set the maximum cache duration for metadata.
+     *  
+     *  <p>Defaults to: 8 hours.</p>
+     *  
+     * @param duration the maximum cache duration
+     */
+    public void setMaxCacheDuration(@Nonnull final Duration duration) {        
+        Constraint.isNotNull(duration, "Duration cannot be null");
+        Constraint.isFalse(duration.isNegative(), "Duration cannot be negative");
+        
+        maxCacheDuration = duration;
+    }
+    
+    /**
+     *  Get the minimum cache duration for metadata.
+     *  
+     *  <p>Defaults to: 10 minutes.</p>
+     *  
+     * @return the minimum cache duration
+     */
+    @Nonnull protected Duration getMinCacheDuration() {
+        return minCacheDuration;
+    }
+    
+
+    /**
+     *  Set the minimum cache duration for metadata.
+     *  
+     *  <p>Defaults to: 10 minutes.</p>
+     *  
+     * @param duration the minimum cache duration
+     */
+    public void setMinCacheDuration(@Nonnull final Duration duration) {
+        Constraint.isNotNull(duration, "Duration cannot be null");
+        Constraint.isFalse(duration.isNegative(), "Duration cannot be negative");
+        
+        minCacheDuration = duration;
+    }
+    
+    /**
+     * Gets the delay factor used to compute the next refresh time.
+     * 
+     * <p>Defaults to:  0.75.</p>
+     * 
+     * @return delay factor used to compute the next refresh time
+     */
+    @Nonnull protected Float getRefreshDelayFactor() {
+        return refreshDelayFactor;
+    }
+    
+
+    /**
+     * Sets the delay factor used to compute the next refresh time. The delay must be between 0.0 and 1.0, exclusive.
+     * 
+     * <p>Defaults to:  0.75.</p>
+     * 
+     * @param factor delay factor used to compute the next refresh time
+     */
+    public void setRefreshDelayFactor(@Nonnull final Float factor) {
+
+        if (factor <= 0 || factor >= 1) {
+            throw new ConstraintViolationException("Refresh delay factor must be a number between 0.0 and 1.0, exclusive");
+        }
+
+        refreshDelayFactor = factor;
+    }
+
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/OIDCProviderMetadataCacheFactoryBean.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/OIDCProviderMetadataCacheFactoryBean.java
deleted file mode 100644
index dedfdc6..0000000
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/OIDCProviderMetadataCacheFactoryBean.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package net.shibboleth.oidc.metadata.cache.impl;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.springframework.beans.factory.FactoryBeanNotInitializedException;
-
-import com.nimbusds.oauth2.sdk.id.Issuer;
-import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
-
-import net.shibboleth.oidc.metadata.cache.MetadataCache;
-import net.shibboleth.oidc.metadata.impl.DefaultBatchBackingStore;
-import net.shibboleth.oidc.metadata.impl.DefaultDynamicBackingStore;
-
-/**
- * Factory bean to create an OIDC specific metadata cache. Either a {@link DynamicMetadataCache} 
- * or {@link BatchMetadataCache} is created depending on the cache properties configured.
- */
-public class OIDCProviderMetadataCacheFactoryBean extends MetadataCacheBuilder<Issuer, OIDCProviderMetadata> {
-    
-    
-    private final AtomicInteger cacheID;
-    
-    /** Constructor.*/
-    public OIDCProviderMetadataCacheFactoryBean() {
-        cacheID = new AtomicInteger(1);
-    }
-
-    @SuppressWarnings("rawtypes")
-    @Override
-    public Class<MetadataCache> getObjectType() {
-        return MetadataCache.class;
-    }
-    
-    @Override
-    protected MetadataCache<OIDCProviderMetadata> createInstance() throws Exception {
-        
-        if (getCacheOperationMode() == CacheOperationMode.DYNAMIC) {
-            final DynamicMetadataCache<Issuer, OIDCProviderMetadata> cache = new DynamicMetadataCache<>(
-                    new DefaultDynamicBackingStore<>(getMaxCacheDuration()), getFetchStrategy());
-            cache.setMinCacheDuration(getMinCacheDuration());
-            //FIXME what did we do with this.
-           // cache.setMaxCacheDuration(getMaxCacheDuration());
-            cache.setRefreshDelayFactor(getRefreshDelayFactor());
-            cache.setMaxIdleEntityData(getMaxIdleEntityData());
-            cache.setMetadataExpirationTimeStrategy(getMetadataExpirationTimeStrategy());
-            cache.setIdentifierExtractionStrategy(getIdentifierExtractionStrategy());
-            cache.setCriteriaToIdentifierStrategy(getCriteriaToIdentifierStrategy());
-            cache.setCleanupTaskInterval(getCleanupTaskInterval());
-            cache.setRemoveIdleEntityData(isRemoveIdleEntityData());
-            cache.setInitialCleanupTaskDelay(getInitialCleanupTaskDelay());
-            cache.setMetadataFilterStrategy(getMetadataFilterStrategy());
-            cache.setMetadataBeforeRemovalHook(getMetadataBeforeRemovalHook());
-            cache.setId("OIDCProviderDynamicMetadataCache"+cacheID.getAndIncrement());
-            cache.initialize();
-            return cache;
-        } else if (getCacheOperationMode() == CacheOperationMode.BATCH) {
-            //TODO why have the backing stores as interfaces if you are setting the concrete type here? inject somehow?
-            final BatchMetadataCache<Issuer, OIDCProviderMetadata> cache = 
-                    new BatchMetadataCache<>(
-                    new DefaultBatchBackingStore<>(), getLoadingStrategy(), getParsingStrategy());
-            cache.setMinCacheDuration(getMinCacheDuration());
-           // cache.setMaxCacheDuration(getMaxCacheDuration());
-            cache.setMinRefreshDelay(getMinRefreshDelay());
-            cache.setMaxRefreshDelay(getMaxRefreshDelay());
-            cache.setRefreshDelayFactor(getRefreshDelayFactor());
-            cache.setMetadataExpirationTimeStrategy(getMetadataExpirationTimeStrategy());
-            cache.setIdentifierExtractionStrategy(getIdentifierExtractionStrategy());
-            cache.setCriteriaToIdentifierStrategy(getCriteriaToIdentifierStrategy());
-            cache.setMetadataFilterStrategy(getMetadataFilterStrategy());
-            cache.setMetadataBeforeRemovalHook(getMetadataBeforeRemovalHook());
-            cache.setId("OIDCProviderBatchMetadataCache"+cacheID.getAndIncrement());
-            cache.initialize();
-            return cache;
-        } else {
-            //should never get here.
-            throw new Exception("Cache mode of operation not supported");
-        }
-        
-    }
-   
-
-
-}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/OIDCProviderMetadataResolver.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/JSONMapMetadataResolver.java
similarity index 76%
copy from oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/OIDCProviderMetadataResolver.java
copy to oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/JSONMapMetadataResolver.java
index 0c44970..fe5aaf9 100644
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/OIDCProviderMetadataResolver.java
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/JSONMapMetadataResolver.java
@@ -17,26 +17,25 @@
 
 package net.shibboleth.oidc.metadata.impl;
 
-import javax.annotation.Nonnull;
+import java.util.Map;
 
-import com.nimbusds.oauth2.sdk.id.Issuer;
-import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import javax.annotation.Nonnull;
 
-import net.shibboleth.oidc.metadata.ProviderMetadataResolver;
+import net.shibboleth.oidc.metadata.JSONMetadataResolver;
 import net.shibboleth.oidc.metadata.cache.MetadataCache;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
 /** Concrete metadata resolver for OIDC Provider configuration metadata resolution.*/
-public class OIDCProviderMetadataResolver extends AbstractOIDCMetadataResolver<Issuer, OIDCProviderMetadata> 
-        implements ProviderMetadataResolver {
+public class JSONMapMetadataResolver 
+        extends AbstractOIDCMetadataResolver<String, Map<String, Object>> implements JSONMetadataResolver {
 
     /**
      * Constructor.
      *
      * @param metadataCache the cache to hold metadata.
      */
-    protected OIDCProviderMetadataResolver(
-            @Nonnull final MetadataCache<OIDCProviderMetadata> metadataCache) {
+    public JSONMapMetadataResolver(
+            @Nonnull final MetadataCache<Map<String, Object>> metadataCache) {
         super(metadataCache);
         
     }
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/OIDCProviderMetadataResolver.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/OIDCProviderMetadataResolver.java
index 0c44970..e119200 100644
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/OIDCProviderMetadataResolver.java
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/OIDCProviderMetadataResolver.java
@@ -35,7 +35,7 @@ public class OIDCProviderMetadataResolver extends AbstractOIDCMetadataResolver<I
      *
      * @param metadataCache the cache to hold metadata.
      */
-    protected OIDCProviderMetadataResolver(
+    public OIDCProviderMetadataResolver(
             @Nonnull final MetadataCache<OIDCProviderMetadata> metadataCache) {
         super(metadataCache);
         
diff --git a/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/cache/impl/BatchMetadataCacheBuilderTest.java b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/cache/impl/BatchMetadataCacheBuilderTest.java
new file mode 100644
index 0000000..34629b3
--- /dev/null
+++ b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/cache/impl/BatchMetadataCacheBuilderTest.java
@@ -0,0 +1,75 @@
+package net.shibboleth.oidc.metadata.cache.impl;
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.
+ */
+
+import static org.testng.Assert.assertNotNull;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.openid.connect.sdk.SubjectType;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.metadata.criterion.IssuerIDCriterion;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+public class BatchMetadataCacheBuilderTest {
+    
+    
+    @Test
+    public void testBatchCacheBuilder_Success() throws ComponentInitializationException {
+        var builder = new BatchMetadataCacheBuilder.Builder<Issuer, OIDCProviderMetadata>();
+        
+        BatchMetadataCacheBuilderSpec<Issuer, OIDCProviderMetadata> spec = new BatchMetadataCacheBuilderSpec<>();
+        spec.setIdentifierExtractionStrategy(m -> m.getIssuer());
+        spec.setMinRefreshDelay(Duration.ofMinutes(5));
+        spec.setMaxRefreshDelay(Duration.ofMinutes(10));
+        spec.setMetadataExpirationTimeStrategy((m, time) -> time.plus(Duration.ofMinutes(5)));
+        spec.setCriteriaToIdentifierStrategy(crit -> {
+            final IssuerIDCriterion issuerId = crit.get(IssuerIDCriterion.class);
+            if (issuerId != null) {
+                return issuerId.getIssuerID();
+            }
+            return null;
+        });
+
+        spec.setRefreshDelayFactor(0.75f);
+        spec.setMinCacheDuration(Duration.ofMinutes(10));
+        // cache.setMaxCacheDuration(Duration.ofMinutes(20));
+        spec.setMetadataFilterStrategy((metadata, context) -> metadata);
+        spec.setLoadingStrategy(context -> "test".getBytes());
+        spec.setParsingStrategy(bytesIn -> {
+            try {
+                return List.of(new OIDCProviderMetadata(new Issuer("http://www.example.org"), 
+                        List.of(SubjectType.PUBLIC),
+                        new URI("http://example.oidc.op.org")));
+            } catch (URISyntaxException e) {
+                return Collections.emptyList();
+            }
+        });
+        final BatchMetadataCache<Issuer, OIDCProviderMetadata> cache = builder.build(spec);
+        assertNotNull(cache);
+        
+    }
+
+}
diff --git a/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/cache/impl/DynamicMetadataCacheBuilderTest.java b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/cache/impl/DynamicMetadataCacheBuilderTest.java
new file mode 100644
index 0000000..3436572
--- /dev/null
+++ b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/cache/impl/DynamicMetadataCacheBuilderTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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 net.shibboleth.oidc.metadata.cache.impl;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Duration;
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.openid.connect.sdk.SubjectType;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.metadata.criterion.IssuerIDCriterion;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+public class DynamicMetadataCacheBuilderTest {
+    
+    
+    @Test
+    public void testDynamicCacheBuilder_Success() throws ComponentInitializationException {
+        var builder = new DynamicMetadataCacheBuilder.Builder<Issuer, OIDCProviderMetadata>();
+        
+        DynamicMetadataCacheBuilderSpec<Issuer, OIDCProviderMetadata> spec = new DynamicMetadataCacheBuilderSpec<>();
+        spec.setIdentifierExtractionStrategy(m -> m.getIssuer());
+
+        spec.setMetadataExpirationTimeStrategy((m, time) -> time.plus(Duration.ofMinutes(5)));
+        spec.setCriteriaToIdentifierStrategy(crit -> {
+            final IssuerIDCriterion issuerId = crit.get(IssuerIDCriterion.class);
+            if (issuerId != null) {
+                return issuerId.getIssuerID();
+            }
+            return null;
+        });
+        spec.setCleanupTaskInterval(Duration.ofSeconds(100));        
+        spec.setInitialCleanupTaskDelay(Duration.ofSeconds(1));
+        spec.setMaxIdleEntityData(Duration.ofMinutes(10));
+        spec.setRefreshDelayFactor(0.75f);
+        spec.setMinCacheDuration(Duration.ofMinutes(10));
+        // cache.setMaxCacheDuration(Duration.ofMinutes(20));
+        spec.setMetadataFilterStrategy((metadata, context) -> metadata);
+        spec.setFetchStrategy(crit -> {
+            try {
+                final Issuer iss = crit.get(IssuerIDCriterion.class).getIssuerID();                       
+                return new OIDCProviderMetadata(iss, List.of(SubjectType.PUBLIC),
+                        new URI("http://example.oidc.op.org"));
+            } catch (URISyntaxException e) {
+                return null;
+            }
+        });
+       
+        final DynamicMetadataCache<Issuer, OIDCProviderMetadata> cache = builder.build(spec);
+        assertNotNull(cache);
+        
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list