[java-shib-attribute] 03/05: JSATTR-41 Look to make the ScriptedDataConnector Cached

Rod Widdowson rdw at steadingsoftware.com
Mon Jan 20 11:45:46 UTC 2025


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

rdw pushed a commit to branch main
in repository java-shib-attribute.

View the commit online:
http://git.shibboleth.net/view/?p=java-shib-attribute.git;a=commit;h=dc1a89c3aaaa60d0d6bf945c89d3ec274933df08

commit dc1a89c3aaaa60d0d6bf945c89d3ec274933df08
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Sat Jan 18 14:34:00 2025 +0000

    JSATTR-41 Look to make the ScriptedDataConnector Cached
    
    https://shibboleth.atlassian.net/browse/JSATTR-41
    
    Derive the ScriptedDataConnector from a slightly modified
    AbstractSerachDataConnector.
    
    Provide a no-opn Validator and MappingStrategy and a minimal mapping
    strategy that allows templated cache keys
---
 .../dc/impl/AbstractSearchDataConnector.java       |  19 ++-
 .../dc/scripted/impl/ScriptedDataConnector.java    |  52 +++++++-
 .../dc/scripted/impl/ScriptedMappingStrategy.java  |  19 +++
 .../resolver/dc/scripted/impl/ScriptedSearch.java  |  19 +++
 .../dc/scripted/impl/ScriptedSearchBuilder.java    | 147 +++++++++++++++++++++
 5 files changed, 246 insertions(+), 10 deletions(-)

diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/impl/AbstractSearchDataConnector.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/impl/AbstractSearchDataConnector.java
index b7db75068..04a6df9ef 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/impl/AbstractSearchDataConnector.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/impl/AbstractSearchDataConnector.java
@@ -163,6 +163,19 @@ public abstract class AbstractSearchDataConnector<T1 extends ExecutableSearch,T2
     @Nullable @Unmodifiable @NotLive protected abstract Map<String,IdPAttribute> retrieveAttributes(
             @Nonnull final T1 executable) throws ResolutionException;
 
+    /** A version of {@link #retrieveAttributes(ExecutableSearch)} that also gets passed the work context.
+     * @param executable used to retrieve data from the data source
+     * @param workContext the work context for the operation.
+     *
+     * @return attributes
+     *
+     * @throws ResolutionException thrown if there is a problem retrieving data from the data source
+     */
+    @Nullable @Unmodifiable @NotLive protected Map<String,IdPAttribute> retrieveAttributes(
+            @Nonnull final T1 executable, @Nonnull AttributeResolverWorkContext workContext) throws ResolutionException {
+           return retrieveAttributes(executable);
+    }
+
     /** {@inheritDoc} */
     @Override
     @Nullable @Unmodifiable @NotLive protected Map<String, IdPAttribute> doDataConnectorResolve(
@@ -184,7 +197,7 @@ public abstract class AbstractSearchDataConnector<T1 extends ExecutableSearch,T2
 
                 if (resolvedAttributes == null) {
                     log.debug("{} Cache key not found", getLogPrefix());
-                    resolvedAttributes = retrieveAttributes(executable);
+                    resolvedAttributes = retrieveAttributes(executable, workContext);
                     log.debug("{} Resolved attributes {}", getLogPrefix(), resolvedAttributes);
                     cache.put(cacheKey, resolvedAttributes != null ? resolvedAttributes : CollectionSupport.emptyMap());
                 } else if (resolvedAttributes.isEmpty()){
@@ -194,11 +207,11 @@ public abstract class AbstractSearchDataConnector<T1 extends ExecutableSearch,T2
                 }
             } else {
                 log.debug("No cache key returned, skipping check for cached results");
-                resolvedAttributes = retrieveAttributes(executable);
+                resolvedAttributes = retrieveAttributes(executable, workContext);
                 log.debug("{} Resolved attributes: {}", getLogPrefix(), resolvedAttributes);
             }
         } else {
-            resolvedAttributes = retrieveAttributes(executable);
+            resolvedAttributes = retrieveAttributes(executable, workContext);
             log.debug("{} Resolved attributes: {}", getLogPrefix(), resolvedAttributes);
         }
 
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedDataConnector.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedDataConnector.java
index 5bb0192f6..88f8b2386 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedDataConnector.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedDataConnector.java
@@ -32,12 +32,15 @@ import org.slf4j.Logger;
 
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.IdPAttributeValue;
-import net.shibboleth.idp.attribute.resolver.AbstractDataConnector;
+import net.shibboleth.idp.attribute.resolver.DataConnector;
 import net.shibboleth.idp.attribute.resolver.PluginDependencySupport;
 import net.shibboleth.idp.attribute.resolver.ResolutionException;
 import net.shibboleth.idp.attribute.resolver.ad.impl.ScriptedIdPAttributeImpl;
 import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
 import net.shibboleth.idp.attribute.resolver.context.AttributeResolverWorkContext;
+import net.shibboleth.idp.attribute.resolver.dc.ValidationException;
+import net.shibboleth.idp.attribute.resolver.dc.Validator;
+import net.shibboleth.idp.attribute.resolver.dc.impl.AbstractSearchDataConnector;
 import net.shibboleth.idp.attribute.resolver.scripted.ResolverScriptContextExtender;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotLive;
@@ -52,7 +55,7 @@ import net.shibboleth.shared.scripting.EvaluableScript;
 /**
  * A Data Connector which populates a series of attributes from a provided {@link ProfileRequestContext}.
  */
-public class ScriptedDataConnector extends AbstractDataConnector {
+public class ScriptedDataConnector extends AbstractSearchDataConnector<ScriptedSearch, ScriptedMappingStrategy>  {
 
     /** The id of the object where the results go. */
     @Nonnull public static final String RESULTS_STRING = "connectorResults";
@@ -75,6 +78,9 @@ public class ScriptedDataConnector extends AbstractDataConnector {
     /** Constructor. */
     public ScriptedDataConnector() {
         contextExtenders = CollectionSupport.emptyList();
+        setValidator(new NullValidator());
+        setMappingStrategy(new ScriptedMappingStrategy());
+        setExecutableSearchBuilder(new ScriptedSearchBuilder());
     }
 
     /**
@@ -146,11 +152,19 @@ public class ScriptedDataConnector extends AbstractDataConnector {
         scriptEvaluator.setContextExtenders(contextExtenders);
         scriptEvaluator.setLogPrefix(getLogPrefix());
     }
-    
+
     /** {@inheritDoc} */
-    @Override @Nullable protected Map<String, IdPAttribute> doDataConnectorResolve(
-            @Nonnull final AttributeResolutionContext resolutionContext,
-            @Nonnull final AttributeResolverWorkContext workContext) throws ResolutionException {
+    @Override
+    protected Map<String, IdPAttribute> retrieveAttributes(ScriptedSearch executable) throws ResolutionException {
+        return Constraint.isNotNull(null, "illegal function called");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected Map<String, IdPAttribute> retrieveAttributes(ScriptedSearch executable,
+         @Nonnull final AttributeResolverWorkContext workContext) throws ResolutionException {
+
+        final AttributeResolutionContext resolutionContext = executable.getResolutionContext();
         Constraint.isNotNull(resolutionContext, "AttributeResolutionContext cannot be null");
         Constraint.isNotNull(workContext, "AttributeResolverWorkContext cannot be null");
         
@@ -290,5 +304,29 @@ public class ScriptedDataConnector extends AbstractDataConnector {
             attribute.setValues(outputValues);
         }
     }
-    
+
+    /**
+      * Because we are a SearchDataConnector we need to have a validator.  This plugs the gap.
+      */
+    private class NullValidator implements Validator {
+
+        private boolean throwValidateError;
+
+        /** {@inheritDoc} */
+        @Override
+        public void validate(DataConnector dataConnector) throws ValidationException {
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void setThrowValidateError(boolean what) {
+            throwValidateError = what;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean isThrowValidateError() {
+            return throwValidateError;
+        }
+    }
 }
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedMappingStrategy.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedMappingStrategy.java
new file mode 100644
index 000000000..07e85f152
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedMappingStrategy.java
@@ -0,0 +1,19 @@
+package net.shibboleth.idp.attribute.resolver.dc.scripted.impl;
+
+import java.util.Map;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.dc.MappingStrategy;
+
+/**
+ * A null mapping strategy for the {@link ScriptedDataConnector}.
+ */
+public class ScriptedMappingStrategy implements MappingStrategy<Map<String,IdPAttribute>> {
+
+    /** {@inheritDoc} */
+    @Override
+    public Map<String, IdPAttribute> map(Map<String, IdPAttribute> results) throws ResolutionException {
+        return results;
+    }
+}
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedSearch.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedSearch.java
new file mode 100644
index 000000000..3776b7037
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedSearch.java
@@ -0,0 +1,19 @@
+package net.shibboleth.idp.attribute.resolver.dc.scripted.impl;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.ExecutableSearch;
+
+/**
+ * {@link ExecutableSearch} for the {@link ScriptedDataConnector}.
+ * This is not a data searcher per se. This interface is here to
+ * provide the cache key (if relevant) and the {@link #AttributeResolutionContext}.
+ */
+public interface  ScriptedSearch extends ExecutableSearch{
+
+    /** Get the context associated with this operation.
+     * @return the context associated with this operation
+     */
+    @Nonnull AttributeResolutionContext getResolutionContext();
+}
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedSearchBuilder.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedSearchBuilder.java
new file mode 100644
index 000000000..c8a3b39e6
--- /dev/null
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/scripted/impl/ScriptedSearchBuilder.java
@@ -0,0 +1,147 @@
+package net.shibboleth.idp.attribute.resolver.dc.scripted.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.ExecutableSearchBuilder;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.velocity.Template;
+
+/**
+ *  {@link ExecutableSearchBuilder} for the {@link ScriptedDataConnector}.
+ */
+public class ScriptedSearchBuilder extends AbstractInitializableComponent
+    implements ExecutableSearchBuilder<ScriptedSearch> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ScriptedSearchBuilder.class);
+
+    /** VelocityEngine. */
+    @NonnullAfterInit private VelocityEngine engine;
+
+    /** Cache key template to be evaluated. */
+    @Nullable private Template cacheKeyTemplate;
+
+    /** Text of cache key template to be evaluated. */
+    @NonnullAfterInit private String cacheKeyTemplateText;
+
+    /** {@inheritDoc} */
+    @Override protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        final VelocityEngine localEngine = engine;
+        if (null == localEngine) {
+            throw new ComponentInitializationException("Velocity engine cannot be null");
+        }
+
+        if (null != cacheKeyTemplateText) {
+            cacheKeyTemplate = Template.fromTemplate(localEngine, cacheKeyTemplateText);
+        }
+    }
+
+    /**
+     * Get the {@link VelocityEngine} to be used.
+     *
+     * @return template engine
+     */
+    @NonnullAfterInit public VelocityEngine getVelocityEngine() {
+        return engine;
+    }
+
+    /**
+     * Set the {@link VelocityEngine} to be used.
+     *
+     * @param velocityEngine engine to be used
+     */
+    public void setVelocityEngine(@Nonnull final VelocityEngine velocityEngine) {
+        checkSetterPreconditions();
+
+        engine = Constraint.isNotNull(velocityEngine, "Velocity engine cannot be null");
+    }
+
+    /**
+     * Get the cache key template text to be evaluated.
+     *
+     * @return template text
+     */
+    @Nullable public String getCacheKeyTemplateText() {
+        return cacheKeyTemplateText;
+    }
+
+    /**
+     * Set the cache key template to be evaluated.
+     *
+     * @param text template to be evaluated
+     */
+    public void setCacheKeyTemplateText(@Nullable final String text) {
+        checkSetterPreconditions();
+
+        cacheKeyTemplateText = StringSupport.trimOrNull(text);
+    }
+
+    @Override
+    public ScriptedSearch build(@Nonnull AttributeResolutionContext resolutionContext,
+                                Map<String, List<IdPAttributeValue>> dependencyAttributes) throws ResolutionException {
+
+        /** {@inheritDoc} */
+        return new ScriptedSearch() {
+
+            @Nonnull public AttributeResolutionContext getResolutionContext() {
+                return resolutionContext;
+            }
+
+            @Nullable @NotEmpty public String getResultCacheKey() {
+                return ScriptedSearchBuilder.this.getResultCacheKey(resolutionContext, dependencyAttributes);
+            }
+
+        };
+    }
+
+    /** Allow a user-provided cache key for this resolution
+     * @param resolutionContext the {@link AttributeResolutionContext}
+     * @param dependencyAttributes the attributes,
+     * @return They key or null if the user didn't specify one.
+     */
+    @Nullable @NotEmpty private String getResultCacheKey(@Nonnull final AttributeResolutionContext resolutionContext,
+                                                         @Nonnull final Map<String, List<IdPAttributeValue>> dependencyAttributes) {
+
+        final Template ckt = cacheKeyTemplate;
+        if (ckt== null) {
+            return null;
+        }
+
+        final VelocityContext context = new VelocityContext();
+        log.trace("Creating cache key using attribute resolution context {}", resolutionContext);
+        context.put("resolutionContext", resolutionContext);
+
+        // inject dependencies
+        if (dependencyAttributes != null && !dependencyAttributes.isEmpty()) {
+            for (final Map.Entry<String, List<IdPAttributeValue>> entry : dependencyAttributes.entrySet()) {
+                final List<Object> values = new ArrayList<>(entry.getValue().size());
+                for (final IdPAttributeValue value : entry.getValue()) {
+                    values.add(value.getNativeValue());
+                }
+                log.trace("Adding dependency {} to context with {} value(s)", entry.getKey(), values.size());
+                context.put(entry.getKey(), values);
+            }
+        }
+        return ckt.merge(context);
+    }
+}

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


More information about the commits mailing list