SLING-9336 - Hide servlet services from the outside

* filter servlet services with a hook
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledHooks.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledHooks.java
new file mode 100644
index 0000000..371df4b
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledHooks.java
@@ -0,0 +1,57 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.scripting.bundle.tracker.internal;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.hooks.service.EventListenerHook;
+import org.osgi.framework.hooks.service.FindHook;
+import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.service.component.annotations.Component;
+
+@Component
+public class BundledHooks implements FindHook, EventListenerHook {
+    @Override
+    public void find(BundleContext context, String name, String filter, boolean allServices, Collection<ServiceReference<?>> references) {
+        if (!context.getBundle().getSymbolicName().equals("org.apache.sling.servlets.resolver")) {
+            for (Iterator<ServiceReference<?>> iter = references.iterator(); iter.hasNext();) {
+                if (iter.next().getProperty(BundledHooks.class.getName()) != null) {
+                    iter.remove();
+                }
+            }
+        }
+    }
+
+
+    @Override
+    public void event(ServiceEvent event, Map<BundleContext, Collection<ListenerHook.ListenerInfo>> listeners) {
+        if (event.getServiceReference().getProperty(BundledHooks.class.getName()) != null) {
+            for (Iterator<Map.Entry<BundleContext, Collection<ListenerHook.ListenerInfo>>> entries = listeners.entrySet().iterator(); entries.hasNext();) {
+                if (!entries.next().getKey().getBundle().getSymbolicName().equals("org.apache.sling.servlets.resolver")) {
+                    entries.remove();
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptTracker.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptTracker.java
index 5d51820..df959d0 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptTracker.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptTracker.java
@@ -182,6 +182,7 @@
                         if (executable.getPath().equals(servletCapability.getPath())) {
                             properties.put(ServletResolverConstants.SLING_SERVLET_PATHS, executable.getPath());
                         }
+                        properties.put(BundledHooks.class.getName(), "true");
                         regs.add(
                                 bundle.getBundleContext().registerService(
                                         Servlet.class,
@@ -238,6 +239,7 @@
                         "=" + rt + "; " +
                         ServletResolverConstants.SLING_SERVLET_EXTENSIONS + "=" + extensions + "; " +
                         ServletResolverConstants.SLING_SERVLET_METHODS + "=" + methods  + "}");
+                properties.put(BundledHooks.class.getName(), "true");
                 reg = registeringBundle.orElse(m_context).registerService(Servlet.class, new DispatcherServlet(rt), properties);
             } else {
                 if (!new HashSet<>(Arrays.asList(PropertiesUtil
diff --git a/src/test/java/org/apache/sling/scripting/bundle/tracker/internal/BundledHooksTest.java b/src/test/java/org/apache/sling/scripting/bundle/tracker/internal/BundledHooksTest.java
new file mode 100644
index 0000000..1b974fb
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/bundle/tracker/internal/BundledHooksTest.java
@@ -0,0 +1,130 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.scripting.bundle.tracker.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.hooks.service.ListenerHook;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class BundledHooksTest {
+    @Test
+    public void testFindHookFiltersOther() {
+        BundledHooks hooks = new BundledHooks();
+
+        BundleContext context = mock(BundleContext.class);
+        Bundle bundle = mock(Bundle.class);
+        ServiceReference<?> ref = mock(ServiceReference.class);
+
+        when(context.getBundle()).thenReturn(bundle);
+        when(bundle.getSymbolicName()).thenReturn("org.apache.sling.foo.bar");
+        when(ref.getProperty(BundledHooks.class.getName())).thenReturn("true");
+        List<ServiceReference<?>> services = new ArrayList<>();
+
+        services.add(ref);
+        hooks.find(context, null, null, true, services);
+
+        Assert.assertTrue(services.isEmpty());
+    }
+
+    @Test
+    public void testFindHookDoesNotFilterResolver() {
+        BundledHooks hooks = new BundledHooks();
+
+        BundleContext context = mock(BundleContext.class);
+        Bundle bundle = mock(Bundle.class);
+        ServiceReference<?> ref = mock(ServiceReference.class);
+
+        when(context.getBundle()).thenReturn(bundle);
+        when(bundle.getSymbolicName()).thenReturn("org.apache.sling.servlets.resolver");
+        when(ref.getProperty(BundledHooks.class.getName())).thenReturn("true");
+        List<ServiceReference<?>> services = new ArrayList<>();
+
+        services.add(ref);
+        hooks.find(context, null, null, true, services);
+
+        Assert.assertEquals(1, services.size());
+    }
+
+    @Test
+    public void testEventHookFiltersOther() {
+        BundledHooks hooks = new BundledHooks();
+
+        ServiceEvent event = mock(ServiceEvent.class);
+
+        BundleContext context = mock(BundleContext.class);
+        Bundle bundle = mock(Bundle.class);
+        ServiceReference ref = mock(ServiceReference.class);
+
+        when(event.getServiceReference()).thenReturn(ref);
+
+        when(context.getBundle()).thenReturn(bundle);
+        when(bundle.getSymbolicName()).thenReturn("org.apache.sling.foo.bar");
+        when(ref.getProperty(BundledHooks.class.getName())).thenReturn("true");
+
+        ListenerHook.ListenerInfo info = mock(ListenerHook.ListenerInfo.class);
+        when(info.getBundleContext()).thenReturn(context);
+        Map<BundleContext, Collection<ListenerHook.ListenerInfo>> listeners = new HashMap<>();
+        listeners.put(context, new ArrayList<>(Arrays.asList(info)));
+
+        hooks.event(event, listeners);
+
+        Assert.assertTrue(listeners.isEmpty());
+    }
+
+    @Test
+    public void testEventHookDoesNotFilterResolver() {
+        BundledHooks hooks = new BundledHooks();
+
+        ServiceEvent event = mock(ServiceEvent.class);
+
+        BundleContext context = mock(BundleContext.class);
+        Bundle bundle = mock(Bundle.class);
+        ServiceReference ref = mock(ServiceReference.class);
+
+        when(event.getServiceReference()).thenReturn(ref);
+
+        when(context.getBundle()).thenReturn(bundle);
+        when(bundle.getSymbolicName()).thenReturn("org.apache.sling.servlets.resolver");
+        when(ref.getProperty(BundledHooks.class.getName())).thenReturn("true");
+
+        ListenerHook.ListenerInfo info = mock(ListenerHook.ListenerInfo.class);
+        when(info.getBundleContext()).thenReturn(context);
+        Map<BundleContext, Collection<ListenerHook.ListenerInfo>> listeners = new HashMap<>();
+        listeners.put(context, new ArrayList<>(Arrays.asList(info)));
+
+        hooks.event(event, listeners);
+
+        Assert.assertEquals(1, listeners.size());
+
+        Assert.assertEquals(1, listeners.get(context).size());
+    }
+}