SLING-9320 - Allow precompiled units to access objects from the same bundle through the Use API

* extended the JavaUseProvider to use the precompiled unit's bundle classloader for
loading classes
* added IT
diff --git a/pom.xml b/pom.xml
index 9e40c9b..942cb77 100644
--- a/pom.xml
+++ b/pom.xml
@@ -122,7 +122,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.scripting.bundle.tracker</artifactId>
-            <version>0.1.0</version>
+            <version>0.1.1-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
 
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/SightlyScriptEngine.java b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/SightlyScriptEngine.java
index d11bdee..c9cab5c 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/SightlyScriptEngine.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/SightlyScriptEngine.java
@@ -70,7 +70,7 @@
         try {
             SightlyCompiledScript compiledScript = null;
             if (precompiledUnitManager != null) {
-                compiledScript = precompiledUnitManager.evaluate(this, scriptContext);
+                compiledScript = precompiledUnitManager.getSightlyCompiledScript(this, scriptContext);
             } else if (slingHTLMasterCompiler != null) {
                 compiledScript = slingHTLMasterCompiler.compileHTLScript(this, reader, scriptContext);
             }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/JavaUseProvider.java b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/JavaUseProvider.java
index 6a45676..faad0de 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/JavaUseProvider.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/JavaUseProvider.java
@@ -18,6 +18,7 @@
  ******************************************************************************/
 package org.apache.sling.scripting.sightly.impl.engine.extension.use;
 
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Modifier;
 import java.util.HashMap;
 import java.util.Map;
@@ -37,6 +38,8 @@
 import org.apache.sling.scripting.sightly.render.RenderContext;
 import org.apache.sling.scripting.sightly.use.ProviderOutcome;
 import org.apache.sling.scripting.sightly.use.UseProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.osgi.framework.Constants;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
@@ -82,71 +85,49 @@
             return ProviderOutcome.failure();
         }
         Bindings globalBindings = renderContext.getBindings();
-        SlingScriptHelper sling = BindingsUtils.getHelper(globalBindings);
         SlingHttpServletRequest request = BindingsUtils.getRequest(globalBindings);
         Map<String, Object> overrides = setRequestAttributes(request, arguments);
 
-        Object result = null;
         try {
+            Exception failure = null;
+            if (precompiledUnitManager != null) {
+                ClassLoader unitClassLoader = precompiledUnitManager.getBundledRenderUnitClassloader(globalBindings);
+                if (unitClassLoader != null) {
+                    try {
+                        Class<?> clazz = unitClassLoader.loadClass(identifier);
+                        return loadObject(clazz, cls -> precompiledUnitManager.getServiceForBundledRenderUnit(globalBindings, clazz),
+                                globalBindings,
+                                arguments);
+                    } catch (Exception e) {
+                        // maybe the class will actually come from the repository
+                        failure = e;
+                    }
+                }
+            }
             if (slingHTLMasterCompiler != null) {
-                result = slingHTLMasterCompiler.getResourceBackedUseObject(renderContext, identifier);
-            }
-            if (result != null) {
-                if (result instanceof Use) {
-                    ((Use) result).init(BindingsUtils.merge(globalBindings, arguments));
-                }
-                return ProviderOutcome.success(result);
-            } else {
-                LOG.debug("Attempting to load class {} from the classloader cache.", identifier);
-                if (precompiledUnitManager != null) {
-                    result = precompiledUnitManager.getBundledRenderUnitDependency(globalBindings, identifier);
-                    if (result != null) {
-                        return ProviderOutcome.success(result);
+                Object result = slingHTLMasterCompiler.getResourceBackedUseObject(renderContext, identifier);
+                if (result != null) {
+                    if (result instanceof Use) {
+                        ((Use) result).init(BindingsUtils.merge(globalBindings, arguments));
                     }
-                }
-                if (slingHTLMasterCompiler != null) {
-                    ClassLoader classLoader = slingHTLMasterCompiler.getClassLoader();
-                    // attempt OSGi service load
-                    if (classLoader != null) {
-                        Class<?> cls = classLoader.loadClass(identifier);
-                        // attempt OSGi service load
-                        result = sling.getService(cls);
-                        if (result != null) {
-                            return ProviderOutcome.success(result);
-                        }
-                        Object adaptableCandidate = arguments.get(ADAPTABLE);
-                        if (adaptableCandidate instanceof Adaptable) {
-                            Adaptable adaptable = (Adaptable) adaptableCandidate;
-                            result = adaptable.adaptTo(cls);
-                            if (result != null) {
-                                return ProviderOutcome.success(result);
-                            }
-                        }
-                        result = request.adaptTo(cls);
-                        if (result == null) {
-                            Resource resource = BindingsUtils.getResource(globalBindings);
-                            result = resource.adaptTo(cls);
-                        }
-                        if (result != null) {
-                            return ProviderOutcome.success(result);
-                        } else if (cls.isInterface() || Modifier.isAbstract(cls.getModifiers())) {
-                            LOG.debug("Wont attempt to instantiate an interface or abstract class {}", cls.getName());
-                            return ProviderOutcome.failure();
-                        } else {
-                            /*
-                             * the object was cached by the class loader but it's not adaptable from {@link Resource} or {@link
-                             * SlingHttpServletRequest}; attempt to load it like a regular POJO that optionally could implement {@link Use}
-                             */
-                            result = cls.newInstance();
-                            if (result instanceof Use) {
-                                ((Use) result).init(BindingsUtils.merge(globalBindings, arguments));
-                            }
-                            return ProviderOutcome.notNullOrFailure(result);
+                    return ProviderOutcome.success(result);
+                } else {
+                    SlingScriptHelper slingScriptHelper = BindingsUtils.getHelper(globalBindings);
+                    if (slingScriptHelper != null) {
+                        try {
+                            Class<?> clazz = slingHTLMasterCompiler.getClassLoader().loadClass(identifier);
+                            return loadObject(clazz, slingScriptHelper::getService, globalBindings, arguments);
+                        } catch (Exception e) {
+                            failure = e;
                         }
                     }
                 }
-                return ProviderOutcome.failure();
+
             }
+            if (failure != null) {
+                return ProviderOutcome.failure(failure);
+            }
+            return ProviderOutcome.failure();
         } catch (Exception e) {
             // any other exception is an error
             return ProviderOutcome.failure(e);
@@ -155,6 +136,51 @@
         }
     }
 
+    private ProviderOutcome loadObject(@NotNull Class<?> cls, @NotNull ServiceLoader serviceLoader, @NotNull Bindings globalBindings,
+                                       @NotNull Bindings arguments)
+            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
+            InstantiationException {
+            // attempt OSGi service load
+            Object result = serviceLoader.getService(cls);
+            if (result != null) {
+                return ProviderOutcome.success(result);
+            }
+            Object adaptableCandidate = arguments.get(ADAPTABLE);
+            if (adaptableCandidate instanceof Adaptable) {
+                Adaptable adaptable = (Adaptable) adaptableCandidate;
+                result = adaptable.adaptTo(cls);
+                if (result != null) {
+                    return ProviderOutcome.success(result);
+                }
+            }
+            SlingHttpServletRequest request = BindingsUtils.getRequest(globalBindings);
+            if (request != null) {
+                result = request.adaptTo(cls);
+            }
+            if (result == null) {
+                Resource resource = BindingsUtils.getResource(globalBindings);
+                if (resource != null) {
+                    result = resource.adaptTo(cls);
+                }
+            }
+            if (result != null) {
+                return ProviderOutcome.success(result);
+            } else if (cls.isInterface() || Modifier.isAbstract(cls.getModifiers())) {
+                LOG.debug("Won't attempt to instantiate an interface or abstract class {}", cls.getName());
+                return ProviderOutcome.failure();
+            } else {
+                /*
+                 * the object was cached by the class loader but it's not adaptable from {@link Resource} or {@link
+                 * SlingHttpServletRequest}; attempt to load it like a regular POJO that optionally could implement {@link Use}
+                 */
+                result = cls.getDeclaredConstructor().newInstance();
+                if (result instanceof Use) {
+                    ((Use) result).init(BindingsUtils.merge(globalBindings, arguments));
+                }
+                return ProviderOutcome.notNullOrFailure(result);
+            }
+    }
+
     private Map<String, Object> setRequestAttributes(ServletRequest request, Bindings arguments) {
         Map<String, Object> overrides = new HashMap<>();
         for (Map.Entry<String, Object> entry : arguments.entrySet()) {
@@ -184,4 +210,8 @@
     }
 
     private static final Object NULL = new Object();
+
+    private interface ServiceLoader {
+        @Nullable Object getService(Class<?> cls);
+    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/UseRuntimeExtension.java b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/UseRuntimeExtension.java
index 242228b..d22f064 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/UseRuntimeExtension.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/UseRuntimeExtension.java
@@ -35,7 +35,6 @@
 import org.apache.sling.scripting.sightly.render.RuntimeObjectModel;
 import org.apache.sling.scripting.sightly.use.ProviderOutcome;
 import org.apache.sling.scripting.sightly.use.UseProvider;
-import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
@@ -86,9 +85,8 @@
             service = UseProvider.class,
             cardinality = ReferenceCardinality.MULTIPLE
     )
-    private void bindUseProvider(ServiceReference serviceReference) {
-        BundleContext bundleContext = serviceReference.getBundle().getBundleContext();
-        providersMap.put(serviceReference, (UseProvider) bundleContext.getService(serviceReference));
+    private void bindUseProvider(ServiceReference<UseProvider> serviceReference, UseProvider provider) {
+        providersMap.put(serviceReference, provider);
     }
 
     private void unbindUseProvider(ServiceReference serviceReference) {
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/precompiled/PrecompiledUnitManager.java b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/precompiled/PrecompiledUnitManager.java
index f880a1d..ca4c8f4 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/precompiled/PrecompiledUnitManager.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/precompiled/PrecompiledUnitManager.java
@@ -25,8 +25,10 @@
 import org.apache.sling.scripting.sightly.impl.engine.SightlyCompiledScript;
 import org.apache.sling.scripting.sightly.impl.engine.SightlyScriptEngine;
 import org.apache.sling.scripting.sightly.render.RenderUnit;
+import org.jetbrains.annotations.Nullable;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.wiring.BundleWiring;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
@@ -34,7 +36,6 @@
 import org.slf4j.LoggerFactory;
 
 @Component(
-        immediate = true,
         service = {}
         /*
          * this component will register itself as a service only if the org.apache.sling.scripting.bundle.tracker API is present
@@ -60,7 +61,7 @@
 
 
     /**
-     * Provides support for evaluating precompiled HTL scripts passed through the {@code scriptContext}. This feature works only when the
+     * Provides support for extracting precompiled HTL scripts passed through the {@code scriptContext}. This feature works only when the
      * {@link org.apache.sling.scripting.bundle.tracker.BundledRenderUnit} API is deployed to the platform as well.
      *
      * @param sightlyScriptEngine the HTL script engine providing access to the HTL runtime
@@ -68,7 +69,8 @@
      * @return an instance of the compiled script, if a precompiled {@link RenderUnit} was present in the {@link ScriptContext}, {@code
      * null} otherwise
      */
-    public SightlyCompiledScript evaluate(SightlyScriptEngine sightlyScriptEngine, ScriptContext scriptContext) {
+    @Nullable
+    public SightlyCompiledScript getSightlyCompiledScript(SightlyScriptEngine sightlyScriptEngine, ScriptContext scriptContext) {
         Bindings bindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
         Object bundledRenderUnit = bindings.get(BundledRenderUnit.VARIABLE);
         if (bundledRenderUnit instanceof BundledRenderUnit) {
@@ -80,11 +82,22 @@
         return null;
     }
 
-    public Object getBundledRenderUnitDependency(Bindings bindings, String identifier) {
+    @Nullable
+    public ClassLoader getBundledRenderUnitClassloader(Bindings bindings) {
         Object bru = bindings.get(BundledRenderUnit.VARIABLE);
         if (bru instanceof BundledRenderUnit) {
             BundledRenderUnit bundledRenderUnit = (BundledRenderUnit) bru;
-            return bundledRenderUnit.getService(identifier);
+            return bundledRenderUnit.getBundle().adapt(BundleWiring.class).getClassLoader();
+        }
+        return null;
+    }
+
+    @Nullable
+    public <T> T getServiceForBundledRenderUnit(Bindings bindings, Class<?> clazz) {
+        Object bru = bindings.get(BundledRenderUnit.VARIABLE);
+        if (bru instanceof BundledRenderUnit) {
+            BundledRenderUnit bundledRenderUnit = (BundledRenderUnit) bru;
+            return bundledRenderUnit.getService(clazz.getName());
         }
         return null;
     }
@@ -93,7 +106,7 @@
         try {
             PrecompiledUnitManager.class.getClassLoader().loadClass("org.apache.sling.scripting.bundle.tracker.BundledRenderUnit");
             return bundleContext.registerService(PrecompiledUnitManager.class, this, null);
-        } catch (Throwable e) {
+        } catch (ClassNotFoundException e) {
             LOGGER.info("No support for precompiled scripts.");
         }
         return null;