[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