SLING-6354 - fix support for SlingBindings injections in ExportServlet
authorJustin Edelson <justin@apache.org>
Thu, 1 Dec 2016 20:59:38 +0000 (20:59 +0000)
committerJustin Edelson <justin@apache.org>
Thu, 1 Dec 2016 20:59:38 +0000 (20:59 +0000)
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1772273 13f79535-47bb-0310-9956-ffa450edef68

pom.xml
src/main/java/org/apache/sling/models/impl/ExportServlet.java
src/main/java/org/apache/sling/models/impl/ExporterScriptEngineFactory.java [new file with mode: 0644]
src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
src/main/java/org/apache/sling/models/impl/ModelPackageBundleListener.java
src/main/java/org/apache/sling/models/impl/injectors/BindingsInjector.java
src/test/java/org/apache/sling/models/impl/OSGiInjectionTest.java
src/test/java/org/apache/sling/models/impl/StaticInjectionAPFLoadOrderTest.java

diff --git a/pom.xml b/pom.xml
index 70fc4e3..1507e3c 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -50,9 +50,9 @@
                     <instructions>
                         <Embed-Dependency>
                             *;scope=compile,
-                            org.osgi.compendium;inline="org/osgi/util/tracker/*"</Embed-Dependency>
-                        <!-- embed the commons.osgi bundle as described in http://njbartlett.name/2014/05/26/static-linking.html -->
-                        <Conditional-Package>org.apache.sling.commons.osgi</Conditional-Package>
+                            org.apache.sling.commons.osgi;inline=true,
+                            org.apache.sling.scripting.core;inline="org/apache/sling/scripting/core/impl/helper/ProtectedBindings.class"
+                        </Embed-Dependency>
                     </instructions>
                 </configuration>
             </plugin>
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.scripting.api</artifactId>
+            <version>2.1.6</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.scripting.core</artifactId>
+            <version>2.0.20</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <scope>provided</scope>
index 693267f..799f820 100644 (file)
 package org.apache.sling.models.impl;
 
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
+import javax.script.ScriptEngineFactory;
+import javax.script.SimpleBindings;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.api.scripting.SlingScriptHelper;
 import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
 import org.apache.sling.models.factory.ExportException;
 import org.apache.sling.models.factory.MissingExporterException;
 import org.apache.sling.models.factory.ModelFactory;
+import org.apache.sling.scripting.api.BindingsValuesProvider;
+import org.apache.sling.scripting.api.BindingsValuesProvidersByContext;
+import org.apache.sling.scripting.core.ScriptHelper;
+import org.apache.sling.scripting.core.impl.helper.ProtectedBindings;
+import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.sling.api.scripting.SlingBindings.*;
+
 @SuppressWarnings("serial")
 class ExportServlet extends SlingSafeMethodsServlet {
 
-    private static final Logger logger = LoggerFactory.getLogger(ExportServlet.class);
+    private final Logger logger;
+
+    /** The context string to use to select BindingsValuesProviders */
+    private static final String BINDINGS_CONTEXT = BindingsValuesProvider.DEFAULT_CONTEXT;
+
+    /** embed this value so as to avoid a dependency on a newer Sling API than otherwise necessary. */
+    private static final String RESOLVER = "resolver";
+
+    /** The set of protected keys. */
+    private static final Set<String> PROTECTED_KEYS =
+            new HashSet<String>(Arrays.asList(REQUEST, RESPONSE, READER, SLING, RESOURCE, RESOLVER, OUT, LOG));
 
     private final String exporterName;
     private final String registeredSelector;
+    private final BundleContext bundleContext;
     private final ModelFactory modelFactory;
+    private final BindingsValuesProvidersByContext bindingsValuesProvidersByContext;
+    private final ScriptEngineFactory scriptEngineFactory;
     private final ExportedObjectAccessor accessor;
     private final Map<String, String> baseOptions;
 
-    public ExportServlet(ModelFactory modelFactory, String registeredSelector, String exporterName, ExportedObjectAccessor accessor,
+    public ExportServlet(BundleContext bundleContext, ModelFactory modelFactory,
+                         BindingsValuesProvidersByContext bindingsValuesProvidersByContext, ScriptEngineFactory scriptFactory,
+                         Class<?> annotatedClass, String registeredSelector, String exporterName, ExportedObjectAccessor accessor,
                          Map<String, String> baseOptions) {
+        this.bundleContext = bundleContext;
         this.modelFactory = modelFactory;
+        this.bindingsValuesProvidersByContext = bindingsValuesProvidersByContext;
+        this.scriptEngineFactory = scriptFactory;
         this.registeredSelector = registeredSelector;
         this.exporterName = exporterName;
         this.accessor = accessor;
         this.baseOptions = baseOptions;
+
+        String loggerName = ExportServlet.class.getName() + "." + annotatedClass.getName();
+        this.logger = LoggerFactory.getLogger(loggerName);
     }
 
     @Override
@@ -59,24 +95,65 @@ class ExportServlet extends SlingSafeMethodsServlet {
             throws ServletException, IOException {
         Map<String, String> options = createOptionMap(request);
 
-        String exported;
+        ScriptHelper scriptHelper = new ScriptHelper(bundleContext, null, request, response);
+
         try {
-            exported = accessor.getExportedString(request, options, modelFactory, exporterName);
-        } catch (ExportException e) {
-            logger.error("Could not perform export with " + exporterName + " requested by model.", e);
-            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-            return;
-        } catch (MissingExporterException e) {
-            logger.error("Could not get exporter " + exporterName + " requested by model.", e);
-            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-            return;
+            addScriptBindings(scriptHelper, request, response);
+            String exported;
+            try {
+                exported = accessor.getExportedString(request, options, modelFactory, exporterName);
+            } catch (ExportException e) {
+                logger.error("Could not perform export with " + exporterName + " requested by model.", e);
+                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                return;
+            } catch (MissingExporterException e) {
+                logger.error("Could not get exporter " + exporterName + " requested by model.", e);
+                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                return;
+            }
+            if (exported == null) {
+                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+                return;
+            }
+            response.setContentType(request.getResponseContentType());
+            response.getWriter().write(exported);
+
+        } finally {
+            scriptHelper.cleanup();
         }
-        if (exported == null) {
-            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
-            return;
+    }
+
+    private void addScriptBindings(SlingScriptHelper scriptHelper, SlingHttpServletRequest request, SlingHttpServletResponse response)
+            throws IOException {
+        SimpleBindings bindings = new SimpleBindings();
+        bindings.put(SLING, scriptHelper);
+        bindings.put(RESOURCE, request.getResource());
+        bindings.put(RESOLVER, request.getResource().getResourceResolver());
+        bindings.put(REQUEST, request);
+        bindings.put(RESPONSE, response);
+        bindings.put(READER, request.getReader());
+        bindings.put(OUT, response.getWriter());
+        bindings.put(LOG, logger);
+
+        final Collection<BindingsValuesProvider> bindingsValuesProviders =
+                bindingsValuesProvidersByContext.getBindingsValuesProviders(scriptEngineFactory, BINDINGS_CONTEXT);
+
+        if (!bindingsValuesProviders.isEmpty()) {
+            Set<String> protectedKeys = new HashSet<String>();
+            protectedKeys.addAll(PROTECTED_KEYS);
+
+            ProtectedBindings protectedBindings = new ProtectedBindings(bindings, protectedKeys);
+            for (BindingsValuesProvider provider : bindingsValuesProviders) {
+                provider.addBindings(protectedBindings);
+            }
+
         }
-        response.setContentType(request.getResponseContentType());
-        response.getWriter().write(exported);
+
+        SlingBindings slingBindings = new SlingBindings();
+        slingBindings.putAll(bindings);
+
+        request.setAttribute(SlingBindings.class.getName(), slingBindings);
+
     }
 
     private Map<String, String> createOptionMap(SlingHttpServletRequest request) {
@@ -104,14 +181,14 @@ class ExportServlet extends SlingSafeMethodsServlet {
         String getExportedString(SlingHttpServletRequest request, Map<String, String> options, ModelFactory modelFactory, String exporterName) throws ExportException, MissingExporterException;
     }
 
-    public static final ExportedObjectAccessor RESOURCE = new ExportedObjectAccessor() {
+    public static final ExportedObjectAccessor RESOURCE_ACCESSOR = new ExportedObjectAccessor() {
         @Override
         public String getExportedString(SlingHttpServletRequest request, Map<String, String> options, ModelFactory modelFactory, String exporterName) throws ExportException, MissingExporterException {
             return modelFactory.exportModelForResource(request.getResource(), exporterName, String.class, options);
         }
     };
 
-    public static final ExportedObjectAccessor REQUEST = new ExportedObjectAccessor() {
+    public static final ExportedObjectAccessor REQUEST_ACCESSOR = new ExportedObjectAccessor() {
         @Override
         public String getExportedString(SlingHttpServletRequest request, Map<String, String> options, ModelFactory modelFactory, String exporterName) throws ExportException, MissingExporterException {
             return modelFactory.exportModelForRequest(request, exporterName, String.class, options);
diff --git a/src/main/java/org/apache/sling/models/impl/ExporterScriptEngineFactory.java b/src/main/java/org/apache/sling/models/impl/ExporterScriptEngineFactory.java
new file mode 100644 (file)
index 0000000..bb0988b
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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.models.impl;
+
+import org.apache.sling.scripting.api.AbstractScriptEngineFactory;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+
+/**
+ * Essentially fake ScriptEngineFactory needed for accessing BindingsValuesProviders in the ExportServlet
+ */
+class ExporterScriptEngineFactory extends AbstractScriptEngineFactory implements ScriptEngineFactory {
+
+    ExporterScriptEngineFactory(Bundle bundle) {
+        super();
+        setEngineName("Apache Sling Models Exporter");
+        // really the only time this is null is during testing
+        if (bundle != null && bundle.getHeaders() != null && bundle.getHeaders().get(Constants.BUNDLE_VERSION) != null) {
+            setEngineVersion(bundle.getHeaders().get(Constants.BUNDLE_VERSION).toString());
+        }
+        setNames("sling-models-exporter");
+    }
+
+    @Override
+    public String getLanguageName() {
+        return null;
+    }
+
+    @Override
+    public String getLanguageVersion() {
+        return null;
+    }
+
+    @Override
+    public ScriptEngine getScriptEngine() {
+        return null;
+    }
+}
index 69ad2e8..8d562f7 100644 (file)
@@ -43,7 +43,6 @@ import javax.annotation.Nonnull;
 import javax.annotation.PostConstruct;
 
 import org.apache.commons.beanutils.PropertyUtils;
-import org.apache.commons.lang.ObjectUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -91,6 +90,7 @@ import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory2;
 import org.apache.sling.models.spi.injectorspecific.StaticInjectAnnotationProcessorFactory;
+import org.apache.sling.scripting.api.BindingsValuesProvidersByContext;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceRegistration;
@@ -193,6 +193,9 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable, ModelFacto
             referenceInterface = ModelExporter.class)
     private final @Nonnull RankedServices<ModelExporter> modelExporters = new RankedServices<ModelExporter>();
 
+    @Reference
+    private BindingsValuesProvidersByContext bindingsValuesProvidersByContext;
+
     ModelPackageBundleListener listener;
 
     final AdapterImplementations adapterImplementations = new AdapterImplementations();
@@ -962,7 +965,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable, ModelFacto
 
         this.jobRegistration = bundleContext.registerService(Runnable.class.getName(), this, properties);
 
-        this.listener = new ModelPackageBundleListener(ctx.getBundleContext(), this, this.adapterImplementations);
+        this.listener = new ModelPackageBundleListener(ctx.getBundleContext(), this, this.adapterImplementations, bindingsValuesProvidersByContext);
 
         Hashtable<Object, Object> printerProps = new Hashtable<Object, Object>();
         printerProps.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");
index 99d7743..02058d4 100644 (file)
@@ -35,6 +35,7 @@ import org.apache.sling.models.annotations.Exporter;
 import org.apache.sling.models.annotations.ExporterOption;
 import org.apache.sling.models.annotations.Exporters;
 import org.apache.sling.models.annotations.Model;
+import org.apache.sling.scripting.api.BindingsValuesProvidersByContext;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
@@ -45,6 +46,7 @@ import org.osgi.util.tracker.BundleTrackerCustomizer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.script.ScriptEngineFactory;
 import javax.servlet.Servlet;
 
 public class ModelPackageBundleListener implements BundleTrackerCustomizer {
@@ -71,13 +73,20 @@ public class ModelPackageBundleListener implements BundleTrackerCustomizer {
     private final ModelAdapterFactory factory;
     
     private final AdapterImplementations adapterImplementations;
+
+    private final BindingsValuesProvidersByContext bindingsValuesProvidersByContext;
+
+    private final ScriptEngineFactory scriptEngineFactory;
     
     public ModelPackageBundleListener(BundleContext bundleContext,
-            ModelAdapterFactory factory,
-            AdapterImplementations adapterImplementations) {
+                                      ModelAdapterFactory factory,
+                                      AdapterImplementations adapterImplementations,
+                                      BindingsValuesProvidersByContext bindingsValuesProvidersByContext) {
         this.bundleContext = bundleContext;
         this.factory = factory;
         this.adapterImplementations = adapterImplementations;
+        this.bindingsValuesProvidersByContext = bindingsValuesProvidersByContext;
+        this.scriptEngineFactory = new ExporterScriptEngineFactory(bundleContext.getBundle());
         this.bundleTracker = new BundleTracker(bundleContext, Bundle.ACTIVE, this);
         this.bundleTracker.open();
     }
@@ -147,9 +156,9 @@ public class ModelPackageBundleListener implements BundleTrackerCustomizer {
                                 adapterImplementations.registerModelToResourceType(bundle, resourceType, adaptable, adapterTypes[0]);
                                 ExportServlet.ExportedObjectAccessor accessor = null;
                                 if (adaptable == Resource.class) {
-                                    accessor = ExportServlet.RESOURCE;
+                                    accessor = ExportServlet.RESOURCE_ACCESSOR;
                                 } else if (adaptable == SlingHttpServletRequest.class) {
-                                    accessor = ExportServlet.REQUEST;
+                                    accessor = ExportServlet.REQUEST_ACCESSOR;
                                 }
                                 Exporter exporterAnnotation = implType.getAnnotation(Exporter.class);
                                 if (exporterAnnotation != null) {
@@ -255,11 +264,12 @@ public class ModelPackageBundleListener implements BundleTrackerCustomizer {
     }
 
 
-    private void registerExporter(Bundle bundle, Class<?> implType, String resourceType, Exporter exporterAnnotation, List<ServiceRegistration> regs,
-                                  ExportServlet.ExportedObjectAccessor accessor) {
+    private void registerExporter(Bundle bundle, Class<?> annotatedClass, String resourceType, Exporter exporterAnnotation,
+                                  List<ServiceRegistration> regs, ExportServlet.ExportedObjectAccessor accessor) {
         if (accessor != null) {
             Map<String, String> baseOptions = getOptions(exporterAnnotation);
-            ExportServlet servlet = new ExportServlet(factory, exporterAnnotation.selector(), exporterAnnotation.name(), accessor, baseOptions);
+            ExportServlet servlet = new ExportServlet(bundle.getBundleContext(), factory, bindingsValuesProvidersByContext,
+                    scriptEngineFactory, annotatedClass, exporterAnnotation.selector(), exporterAnnotation.name(), accessor, baseOptions);
             Dictionary<String, Object> registrationProps = new Hashtable<String, Object>();
             registrationProps.put("sling.servlet.resourceTypes", resourceType);
             registrationProps.put("sling.servlet.selectors", exporterAnnotation.selector());
index 740c369..2960d76 100644 (file)
@@ -59,12 +59,7 @@ public class BindingsInjector implements Injector, StaticInjectAnnotationProcess
         if (bindings == null) {
             return null;
         }
-        if (type instanceof Class<?>) {
-            return bindings.get(name);
-        } else {
-            log.debug("BindingsInjector doesn't support non-class type {}", type);
-            return null;
-        }
+        return bindings.get(name);
     }
 
     private SlingBindings getBindings(Object adaptable) {
index 0ac19a7..166545f 100644 (file)
@@ -220,6 +220,7 @@ public class OSGiInjectionTest {
         verify(bundleContext).addBundleListener(any(BundleListener.class));
         verify(bundleContext).registerService(eq(Object.class.getName()), any(Object.class), any(Dictionary.class));
         verify(bundleContext).getBundles();
+        verify(bundleContext).getBundle();
         verifyNoMoreInteractions(res, bundleContext);
     }
 
index 70c7c27..41334e0 100644 (file)
@@ -29,6 +29,7 @@ import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
 import org.apache.sling.models.annotations.injectorspecific.SlingObject;
 import org.apache.sling.models.impl.injectors.SlingObjectInjector;
 import org.apache.sling.models.testutil.ModelAdapterFactoryUtil;
+import org.apache.sling.scripting.api.BindingsValuesProvidersByContext;
 import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
 import org.junit.Before;
 import org.junit.Rule;
@@ -50,12 +51,15 @@ public class StaticInjectionAPFLoadOrderTest {
     private SlingHttpServletRequest request;
     @Mock
     private ResourceResolver resourceResolver;
+
+    @Mock
+    private BindingsValuesProvidersByContext bindingsValuesProvidersByContext;
     
     private ModelAdapterFactory factory;
     
     @Before
     public void setUp() {
-        registerModelAdapterFactory();
+        registerServices();
     }
     
     /**
@@ -112,7 +116,8 @@ public class StaticInjectionAPFLoadOrderTest {
         assertTrue(createModel().hasResourceResolver());
     }
     
-    private void registerModelAdapterFactory() {
+    private void registerServices() {
+        context.registerService(BindingsValuesProvidersByContext.class, bindingsValuesProvidersByContext);
         factory = context.registerInjectActivateService(new ModelAdapterFactory());
     }