SLING-9680 support custom BindingsValuesProvider in a way that keeps the dynamic nature of the "OOTB" bindings depending on current resource
diff --git a/core/src/main/java/org/apache/sling/testing/mock/sling/context/MockSlingBindings.java b/core/src/main/java/org/apache/sling/testing/mock/sling/context/MockSlingBindings.java
index 0f6f464..8bcb9d8 100644
--- a/core/src/main/java/org/apache/sling/testing/mock/sling/context/MockSlingBindings.java
+++ b/core/src/main/java/org/apache/sling/testing/mock/sling/context/MockSlingBindings.java
@@ -20,28 +20,39 @@
import javax.jcr.Node;
import javax.jcr.Session;
+import javax.script.SimpleBindings;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.scripting.api.BindingsValuesProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
/**
* Mock extension of {@link SlingBindings} that dynamically evaluates properties read from SlingBindings from the current mock context.
* Normally the SlingBingings are set statically for each script execution, but in mocks where no script is really executed
* it's easier to evaluate them from current context.
*/
-class MockSlingBindings extends SlingBindings {
+class MockSlingBindings extends SlingBindings implements EventHandler {
private static final long serialVersionUID = 1L;
private static final String PROP_CURRENT_NODE = "currentNode";
private static final String PROP_CURRENT_SESSION = "currentSession";
+ /**
+ * OSGi service property to set to "true" on BindingsValuesProvider implementations that should be ignored
+ * when populating the "non-dynamic" bindings properties.
+ */
+ static final String SERVICE_PROPERTY_MOCK_SLING_BINDINGS_IGNORE = "MockSlingBindings-ignore";
+
private final SlingContextImpl context;
public MockSlingBindings(SlingContextImpl context) {
this.context = context;
+ populateFromBindingsValuesProvider();
}
@Override
@@ -54,7 +65,27 @@
}
return super.get(key);
}
-
+
+ /**
+ * Removes all (non-dynamic) properties from bindings and populates them from all registered {@link BindingsValuesProvider} implementations.
+ */
+ private void populateFromBindingsValuesProvider() {
+ SimpleBindings bindings = new SimpleBindings();
+ for (BindingsValuesProvider provider : context.getServices(BindingsValuesProvider.class,
+ "(!(" + SERVICE_PROPERTY_MOCK_SLING_BINDINGS_IGNORE + "=true))")) {
+ provider.addBindings(bindings);
+ }
+ this.clear();
+ this.putAll(bindings);
+ }
+
+ @Override
+ public void handleEvent(Event event) {
+ // is triggered by OSGi events fired by {@link org.apache.sling.scripting.core.impl.BindingsValuesProvidersByContextImpl}
+ // whenever a new bindings value provider is added or removed
+ populateFromBindingsValuesProvider();
+ }
+
static @Nullable Object resolveSlingBindingProperty(@NotNull SlingContextImpl context, @NotNull String property) {
// -- Sling --
@@ -79,7 +110,7 @@
if (StringUtils.equals(property, OUT)) {
return context.response().getWriter();
}
-
+
// -- JCR --
// this emulates behavior of org.apache.sling.jcr.resource.internal.scripting.JcrObjectsBindingsValuesProvider
if (StringUtils.equals(property, PROP_CURRENT_NODE)) {
@@ -91,7 +122,7 @@
if (StringUtils.equals(property, PROP_CURRENT_SESSION)) {
return context.resourceResolver().adaptTo(Session.class);
}
-
+
return null;
}
diff --git a/core/src/main/java/org/apache/sling/testing/mock/sling/context/SlingContextImpl.java b/core/src/main/java/org/apache/sling/testing/mock/sling/context/SlingContextImpl.java
index 7389b97..8211bda 100644
--- a/core/src/main/java/org/apache/sling/testing/mock/sling/context/SlingContextImpl.java
+++ b/core/src/main/java/org/apache/sling/testing/mock/sling/context/SlingContextImpl.java
@@ -18,6 +18,9 @@
*/
package org.apache.sling.testing.mock.sling.context;
+import static org.apache.sling.testing.mock.sling.context.MockSlingBindings.SERVICE_PROPERTY_MOCK_SLING_BINDINGS_IGNORE;
+import static org.osgi.service.event.EventConstants.EVENT_TOPIC;
+
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -63,6 +66,7 @@
import org.osgi.annotation.versioning.ConsumerType;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
+import org.osgi.service.event.EventHandler;
/**
* Defines Sling context objects with lazy initialization.
@@ -180,7 +184,8 @@
registerService(SlingSettingsService.class, new MockSlingSettingService(DEFAULT_RUN_MODES));
registerService(MimeTypeService.class, new MockMimeTypeService());
registerInjectActivateService(new ResourceBuilderFactoryService());
- registerInjectActivateService(new JcrObjectsBindingsValuesProvider());
+ registerInjectActivateService(new JcrObjectsBindingsValuesProvider(),
+ SERVICE_PROPERTY_MOCK_SLING_BINDINGS_IGNORE, true);
registerInjectActivateService(new MockResourceBundleProvider());
registerInjectActivateService(new MockXSSAPIImpl());
@@ -283,7 +288,12 @@
this.request = new MockSlingHttpServletRequest(this.resourceResolver(), this.bundleContext());
// initialize sling bindings
- SlingBindings bindings = new MockSlingBindings(this);
+ MockSlingBindings bindings = new MockSlingBindings(this);
+
+ // register as OSGi event handler to get notified on events fired by BindingsValuesProvidersByContextImpl
+ this.registerService(EventHandler.class, bindings,
+ EVENT_TOPIC, "org/apache/sling/scripting/core/BindingsValuesProvider/*");
+
this.request.setAttribute(SlingBindings.class.getName(), bindings);
}
return this.request;
diff --git a/core/src/test/java/org/apache/sling/testing/mock/sling/SlingBindingsTest.java b/core/src/test/java/org/apache/sling/testing/mock/sling/SlingBindingsTest.java
deleted file mode 100644
index 9f1f823..0000000
--- a/core/src/test/java/org/apache/sling/testing/mock/sling/SlingBindingsTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF 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 org.apache.sling.testing.mock.sling;
-
-import static org.junit.Assert.assertNotNull;
-
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.testing.mock.sling.context.models.SlingBindingsModel;
-import org.apache.sling.testing.mock.sling.junit.SlingContext;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-@SuppressWarnings("null")
-public class SlingBindingsTest {
-
- @Rule
- public SlingContext context = new SlingContext(ResourceResolverType.JCR_MOCK);
-
- private Resource currentResource;
-
- @Before
- public void setUp() throws Exception {
- context.addModelsForClasses(SlingBindingsModel.class);
- currentResource = context.create().resource("/content/testPage/testResource");
- context.currentResource(currentResource);
- }
-
- @Test
- public void testBindings() {
- SlingBindingsModel model = context.request().adaptTo(SlingBindingsModel.class);
-
- assertNotNull(model);
- assertNotNull(model.getResolver());
- assertNotNull(model.getResource());
- assertNotNull(model.getRequest());
- assertNotNull(model.getResponse());
- assertNotNull(model.getCurrentNode());
- assertNotNull(model.getcurrentSession());
- }
-
-}
diff --git a/core/src/test/java/org/apache/sling/testing/mock/sling/context/SlingBindingsTest.java b/core/src/test/java/org/apache/sling/testing/mock/sling/context/SlingBindingsTest.java
new file mode 100644
index 0000000..7a9495d
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/testing/mock/sling/context/SlingBindingsTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.sling.testing.mock.sling.context;
+
+import static org.apache.sling.testing.mock.sling.context.MockSlingBindings.SERVICE_PROPERTY_MOCK_SLING_BINDINGS_IGNORE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import javax.script.Bindings;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.scripting.api.BindingsValuesProvider;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.context.models.SlingBindingsModel;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+@SuppressWarnings("null")
+public class SlingBindingsTest {
+
+ @Rule
+ public SlingContext context = new SlingContext(ResourceResolverType.JCR_MOCK);
+
+ private Resource currentResource;
+
+ @Before
+ public void setUp() throws Exception {
+ // setup a custom BindingsValuesProvider
+ context.registerService(BindingsValuesProvider.class, new BindingsValuesProvider() {
+ @Override
+ public void addBindings(Bindings bindings) {
+ bindings.put("custom-param-1", "value-1");
+ }
+ });
+
+ // setup another custom BindingsValuesProvider which should be ignored
+ context.registerService(BindingsValuesProvider.class, new BindingsValuesProvider() {
+ @Override
+ public void addBindings(Bindings bindings) {
+ bindings.put("custom-param-2", "value-2");
+ }
+ }, SERVICE_PROPERTY_MOCK_SLING_BINDINGS_IGNORE, true);
+
+ context.addModelsForClasses(SlingBindingsModel.class);
+ currentResource = context.create().resource("/content/testPage/testResource");
+ context.currentResource(currentResource);
+
+ // setup a custom BindingsValuesProvider after touching request first time/setting current resource
+ context.registerService(BindingsValuesProvider.class, new BindingsValuesProvider() {
+ @Override
+ public void addBindings(Bindings bindings) {
+ bindings.put("custom-param-3", "value-3");
+ }
+ });
+ // wait a short time to get sure OSGi events get distributed announcing the new BindingsValueProvider
+ Thread.sleep(25);
+ }
+
+ @Test
+ public void testModelBindings() {
+ SlingBindingsModel model = context.request().adaptTo(SlingBindingsModel.class);
+
+ assertNotNull(model);
+ assertNotNull(model.getResolver());
+ assertNotNull(model.getResource());
+ assertEquals(currentResource.getPath(), model.getResource().getPath());
+ assertNotNull(model.getRequest());
+ assertNotNull(model.getResponse());
+ assertNotNull(model.getCurrentNode());
+ assertNotNull(model.getCurrentSession());
+ assertEquals("value-1", model.getCustomParam1());
+ assertNull(model.getCustomParam2());
+ assertEquals("value-3", model.getCustomParam3());
+ }
+
+ @Test
+ public void testCustomBindingsValuesProvider() {
+ SlingBindings bindings = (SlingBindings)context.request().getAttribute(SlingBindings.class.getName());
+ assertNotNull(bindings);
+ assertEquals(currentResource.getPath(), bindings.getResource().getPath());
+ assertEquals("value-1", bindings.get("custom-param-1"));
+ assertNull(bindings.get("custom-param-2"));
+ assertEquals("value-3", bindings.get("custom-param-3"));
+ }
+
+}
diff --git a/core/src/test/java/org/apache/sling/testing/mock/sling/context/models/SlingBindingsModel.java b/core/src/test/java/org/apache/sling/testing/mock/sling/context/models/SlingBindingsModel.java
index 0d26bea..6a15464 100644
--- a/core/src/test/java/org/apache/sling/testing/mock/sling/context/models/SlingBindingsModel.java
+++ b/core/src/test/java/org/apache/sling/testing/mock/sling/context/models/SlingBindingsModel.java
@@ -50,6 +50,16 @@
Node getCurrentNode();
@ScriptVariable(injectionStrategy = InjectionStrategy.OPTIONAL)
- Session getcurrentSession();
+ Session getCurrentSession();
+
+ // -- Custom --
+ @ScriptVariable(name = "custom-param-1", injectionStrategy = InjectionStrategy.OPTIONAL)
+ String getCustomParam1();
+
+ @ScriptVariable(name = "custom-param-2", injectionStrategy = InjectionStrategy.OPTIONAL)
+ String getCustomParam2();
+
+ @ScriptVariable(name = "custom-param-3", injectionStrategy = InjectionStrategy.OPTIONAL)
+ String getCustomParam3();
}