FELIX-4545 : Implement Servlet Context Helper

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1655970 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/ExtenderManager.java b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/ExtenderManager.java
index 158a9fc..b5b6e24 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/ExtenderManager.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/ExtenderManager.java
@@ -20,7 +20,6 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Set;
 
 import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
@@ -30,21 +29,17 @@
 import org.apache.felix.http.base.internal.runtime.FilterInfo;
 import org.apache.felix.http.base.internal.runtime.ServletInfo;
 import org.apache.felix.http.base.internal.service.HttpServiceImpl;
-import org.apache.felix.http.base.internal.whiteboard.HttpContextManager.HttpContextHolder;
 import org.apache.felix.http.base.internal.whiteboard.tracker.FilterTracker;
 import org.apache.felix.http.base.internal.whiteboard.tracker.ServletContextHelperTracker;
 import org.apache.felix.http.base.internal.whiteboard.tracker.ServletTracker;
-import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.http.HttpService;
-import org.osgi.service.http.context.ServletContextHelper;
 import org.osgi.service.http.runtime.dto.ResourceDTO;
 import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
 import org.osgi.util.tracker.ServiceTracker;
 
-@SuppressWarnings({ "deprecation" })
 public final class ExtenderManager
 {
     static final String TYPE_RESOURCE = "r";
@@ -62,33 +57,35 @@
     public static final String FILTER_INIT_PREFIX = "filter.init.";
 
     private final Map<String, AbstractMapping> mapping;
-    private final HttpContextManager contextManager;
+
+    private final ServletContextHelperManager contextManager;
 
     private final HttpService httpService;
 
-    private final ArrayList<ServiceTracker> trackers = new ArrayList<ServiceTracker>();
+    private final ArrayList<ServiceTracker<?, ?>> trackers = new ArrayList<ServiceTracker<?, ?>>();
 
     public ExtenderManager(final HttpService httpService, final BundleContext bundleContext)
     {
         this.mapping = new HashMap<String, AbstractMapping>();
-        this.contextManager = new HttpContextManager();
+        this.contextManager = new ServletContextHelperManager();
         this.httpService = httpService;
         addTracker(new FilterTracker(bundleContext, this));
         addTracker(new ServletTracker(bundleContext, this));
-        addTracker(new ServletContextHelperTracker(bundleContext, this));
+        addTracker(new ServletContextHelperTracker(bundleContext, this.contextManager));
     }
 
     public void close()
     {
-        for(final ServiceTracker t : this.trackers)
+        for(final ServiceTracker<?, ?> t : this.trackers)
         {
             t.close();
         }
         this.trackers.clear();
         this.unregisterAll();
+        this.contextManager.close();
     }
 
-    private void addTracker(ServiceTracker tracker)
+    private void addTracker(ServiceTracker<?, ?> tracker)
     {
         this.trackers.add(tracker);
         tracker.open();
@@ -197,26 +194,6 @@
         return result;
     }
 
-    public void add(ServletContextHelper service, ServiceReference ref)
-    {
-        String name = getStringProperty(ref, HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME);
-        String path = getStringProperty(ref, HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH);
-
-        // TODO - check if name and path are valid values
-        if (!isEmpty(name) && !isEmpty(path) )
-        {
-            Collection<AbstractMapping> mappings = this.contextManager.addContextHelper(ref.getBundle(), name, path, service);
-            for (AbstractMapping mapping : mappings)
-            {
-                registerMapping(mapping);
-            }
-        }
-        else
-        {
-            SystemLogger.debug("Ignoring ServletContextHelper Service " + ref + ", " + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + " is missing or empty");
-        }
-    }
-
     public void addResource(final ServiceReference ref)
     {
         final String[] pattern = getStringArrayProperty(ref, HttpWhiteboardConstants.HTTP_WHITEBOARD_RESOURCE_PATTERN);
@@ -252,30 +229,6 @@
         this.removeMapping(TYPE_RESOURCE, ref);
     }
 
-    public void remove(ServletContextHelper service)
-    {
-        Collection<AbstractMapping> mappings = this.contextManager.removeContextHelper(service);
-        if (mappings != null)
-        {
-            for (AbstractMapping mapping : mappings)
-            {
-                unregisterMapping(mapping);
-            }
-        }
-    }
-
-    private void ungetHttpContext(AbstractMapping mapping, ServiceReference ref)
-    {
-        Bundle bundle = ref.getBundle();
-        String contextName = getStringProperty(ref, HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT);
-        if (!isEmpty(contextName))
-        {
-            this.contextManager.ungetHttpContext(bundle, contextName, mapping, true);
-            return;
-        }
-        this.contextManager.ungetHttpContext(bundle, null, mapping);
-    }
-
     public void add(final Filter service, final ServiceReference ref)
     {
         final FilterInfo filterInfo = createFilterInfo(ref, true);
@@ -341,7 +294,7 @@
         return servletInfo;
     }
 
-    public void add(Servlet service, ServiceReference<?> ref)
+    public void add(Servlet service, ServiceReference<Servlet> ref)
     {
         final ServletInfo servletInfo = createServletInfo(ref, true);
         if ( servletInfo != null )
@@ -350,7 +303,7 @@
         }
     }
 
-    public void removeFilter(final Filter service, ServiceReference ref)
+    public void removeFilter(final Filter service, ServiceReference<Filter> ref)
     {
         final FilterInfo filterInfo = createFilterInfo(ref, false);
         if ( filterInfo != null )
@@ -359,7 +312,7 @@
         }
     }
 
-    public void removeServlet(Servlet service, ServiceReference ref)
+    public void removeServlet(Servlet service, ServiceReference<Servlet> ref)
     {
         final ServletInfo servletInfo = createServletInfo(ref, false);
         if ( servletInfo != null )
@@ -401,7 +354,6 @@
         AbstractMapping mapping = this.mapping.remove(ref.getProperty(Constants.SERVICE_ID).toString().concat(servType));
         if (mapping != null)
         {
-            ungetHttpContext(mapping, ref);
             unregisterMapping(mapping);
         }
     }
@@ -423,36 +375,4 @@
             mapping.unregister(httpService);
         }
     }
-
-    /**
-     * Returns
-     * {@link org.apache.felix.http.base.internal.whiteboard.whiteboard.internal.manager.HttpContextManager.HttpContextHolder}
-     * instances of HttpContext services.
-     *
-     * @return
-     */
-    Map<String, HttpContextHolder> getHttpContexts()
-    {
-        return this.contextManager.getHttpContexts();
-    }
-
-    /**
-     * Returns {@link AbstractMapping} instances for which there is no
-     * registered HttpContext as desired by the context ID.
-     */
-    Map<String, Set<AbstractMapping>> getOrphanMappings()
-    {
-        return this.contextManager.getOrphanMappings();
-    }
-
-    /**
-     * Returns mappings indexed by there owning OSGi service.
-     */
-    Map<String, AbstractMapping> getMappings()
-    {
-        synchronized (this)
-        {
-            return new HashMap<String, AbstractMapping>(this.mapping);
-        }
-    }
-}
+ }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/HttpContextManager.java b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/HttpContextManager.java
deleted file mode 100644
index c610284..0000000
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/HttpContextManager.java
+++ /dev/null
@@ -1,357 +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.felix.http.base.internal.whiteboard;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.felix.http.base.internal.logger.SystemLogger;
-import org.osgi.framework.Bundle;
-import org.osgi.service.http.HttpContext;
-import org.osgi.service.http.context.ServletContextHelper;
-
-public final class HttpContextManager
-{
-    /**
-     * HttpContextHolders indexed by context ID fully configured
-     * with an HttpContext and optional servlets and filters.
-     * <p>
-     * The context ID either includes the bundle ID as the first part of the
-     * name, such as <i>123-sample.context</i> in the case of non-shared
-     * contexts. IDs of shared contexts are prefixed with the fixed string
-     * <code>shared</code> to not mix them with per-bundle contexts.
-     */
-    private final HashMap<String, HttpContextHolder> idMap;
-
-    /**
-     * Reverse mapping of HttpContext services to the context ID or
-     * name with which they are registered.
-     */
-    private final HashMap<HttpContext, String> contextMap;
-
-    /**
-     * Mapping of registered ServletContextHelper services to the
-     * HttpContext stored in <code>contextMap</code>.
-     */
-    private final HashMap<ServletContextHelper, HttpContext> helperMap;
-
-    /**
-     * Map of servlets and filters registered referring to unregistered
-     * contexts as of yet.
-     */
-    private final HashMap<String, Set<AbstractMapping>> orphanMappings;
-
-    public HttpContextManager()
-    {
-        this.idMap = new HashMap<String, HttpContextHolder>();
-        this.contextMap = new HashMap<HttpContext, String>();
-        this.helperMap = new HashMap<ServletContextHelper, HttpContext>();
-        this.orphanMappings = new HashMap<String, Set<AbstractMapping>>();
-    }
-
-    private static String createId(Bundle bundle, String contextId, boolean isContextHelper)
-    {
-        if (isContextHelper)
-        {
-            return "servletcontexthelper-" + ((contextId == null) ? "" : contextId);
-        }
-        if (bundle != null)
-        {
-            return bundle.getBundleId() + "-" + ((contextId == null) ? "" : contextId);
-        }
-
-        return createId(contextId);
-    }
-
-    private static String createId(String contextId)
-    {
-        return "shared-" + ((contextId == null) ? "" : contextId);
-    }
-
-    private static String getContextId(String id)
-    {
-        final int dash = id.indexOf('-');
-        return (dash < 0) ? id : id.substring(dash + 1);
-    }
-
-    public synchronized HttpContext getHttpContext(Bundle bundle, String contextId, AbstractMapping mapping)
-    {
-        return getHttpContext(bundle, contextId, mapping, false);
-    }
-
-    public synchronized HttpContext getHttpContext(Bundle bundle, String contextId, AbstractMapping mapping,
-                                                   boolean isContextHelper)
-    {
-        // per-bundle context
-        String id = createId(bundle, contextId, isContextHelper);
-        HttpContextHolder holder = this.idMap.get(id);
-
-        // shared context
-        if (holder == null)
-        {
-            id = createId(contextId);
-            holder = this.idMap.get(id);
-        }
-
-        // no context yet, put the mapping on hold
-        if (holder == null)
-        {
-            // care for default context if no context ID
-            if (ExtenderManager.isEmpty(contextId))
-            {
-                addHttpContext(bundle, "", new DefaultHttpContext(bundle));
-                return getHttpContext(bundle, "", mapping);
-            }
-
-            // otherwise context is not here yet
-            Set<AbstractMapping> orphaned = this.orphanMappings.get(contextId);
-            if (orphaned == null)
-            {
-                orphaned = new HashSet<AbstractMapping>();
-                this.orphanMappings.put(contextId, orphaned);
-            }
-            if (contextId != null)
-            {
-                // Only log something when an actual context ID is used. Should solve FELIX-4307...
-                SystemLogger.debug("Holding off mapping with unregistered context with id [" + contextId + "]");
-            }
-            orphaned.add(mapping);
-            return null;
-        }
-
-        // otherwise use the context
-        if (contextId != null)
-        {
-            // Only log something when an actual context ID is used. Should solve FELIX-4307...
-            SystemLogger.debug("Reusing context with id [" + contextId + "]");
-        }
-
-        holder.addMapping(mapping);
-        return holder.getContext();
-    }
-
-    public synchronized void ungetHttpContext(Bundle bundle, String contextId, AbstractMapping mapping)
-    {
-        ungetHttpContext(bundle, contextId, mapping, false);
-    }
-
-    public synchronized void ungetHttpContext(Bundle bundle, String contextId,
-                                              AbstractMapping mapping, boolean isContextHelper)
-    {
-        // per-bundle context
-        String id = createId(bundle, contextId, isContextHelper);
-        HttpContextHolder context = this.idMap.get(id);
-
-        // shared context
-        if (context == null && !isContextHelper)
-        {
-            id = createId(contextId);
-            context = this.idMap.get(id);
-        }
-
-        // remove the mapping if there is a mapped context
-        if (context != null)
-        {
-            context.removeMapping(mapping);
-        }
-        else
-        {
-            Set<AbstractMapping> orphans = this.orphanMappings.get(contextId);
-            if (orphans != null)
-            {
-                orphans.remove(mapping);
-                if (orphans.isEmpty())
-                {
-                    this.orphanMappings.remove(contextId);
-                }
-            }
-
-            // it is not expected but make sure there is no reference
-            mapping.setContext(null);
-        }
-    }
-
-    public synchronized Collection<AbstractMapping> addHttpContext(Bundle bundle, String contextId, HttpContext context)
-    {
-        String id = createId(bundle, contextId, false);
-        HttpContextHolder holder = new HttpContextHolder(context);
-
-        Set<AbstractMapping> orphans = this.orphanMappings.remove(contextId);
-        if (orphans != null)
-        {
-            for (Iterator<AbstractMapping> mi = orphans.iterator(); mi.hasNext();)
-            {
-                AbstractMapping mapping = mi.next();
-                if (bundle == null || bundle.equals(mapping.getBundle()))
-                {
-                    holder.addMapping(mapping);
-                    mi.remove();
-                }
-            }
-
-            // put any remaining orphans back
-            if (!orphans.isEmpty())
-            {
-                this.orphanMappings.put(contextId, orphans);
-            }
-        }
-
-        this.idMap.put(id, holder);
-        this.contextMap.put(context, id);
-        
-        // return a copy to prevent concurrent modification
-        return new HashSet<AbstractMapping>(holder.getMappings());
-    }
-
-    public synchronized Collection<AbstractMapping> addContextHelper(Bundle bundle, String contextName, String contextPath,
-                                                                     ServletContextHelper contextHelper)
-    {
-        String id = createId(bundle, contextName, true);
-        HttpContextHolder holder = new HttpContextHolder(contextHelper, contextPath);
-
-        Set<AbstractMapping> orphans = this.orphanMappings.remove(contextName);
-        if (orphans != null)
-        {
-            for (Iterator<AbstractMapping> mi = orphans.iterator(); mi.hasNext();)
-            {
-                AbstractMapping mapping = mi.next();
-                if (bundle == null || bundle.equals(mapping.getBundle()))
-                {
-                    holder.addMapping(mapping);
-                    mi.remove();
-                }
-            }
-
-            // put any remaining orphans back
-            if (!orphans.isEmpty())
-            {
-                this.orphanMappings.put(contextName, orphans);
-            }
-        }
-
-        this.idMap.put(id, holder);
-        this.contextMap.put(holder.getContext(), id);
-        this.helperMap.put(contextHelper, holder.getContext());
-
-        return holder.getMappings();
-    }
-
-    public synchronized Collection<AbstractMapping> removeHttpContext(HttpContext context)
-    {
-        String id = this.contextMap.remove(context);
-        if (id != null)
-        {
-            HttpContextHolder holder = this.idMap.remove(id);
-            if (holder != null)
-            {
-                Set<AbstractMapping> mappings = holder.getMappings();
-                if (mappings != null && !mappings.isEmpty())
-                {
-                    // keep the orphans around
-                    final String contextId = getContextId(id);
-                    Set<AbstractMapping> orphans = this.orphanMappings.get(contextId);
-                    if (orphans == null)
-                    {
-                        orphans = new HashSet<AbstractMapping>();
-                        this.orphanMappings.put(getContextId(id), orphans);
-                    }
-
-                    for (AbstractMapping mapping : mappings)
-                    {
-                        mapping.setContext(null);
-                        orphans.add(mapping);
-                    }
-                }
-                return mappings;
-            }
-        }
-        return null;
-    }
-
-    public synchronized Collection<AbstractMapping> removeContextHelper(ServletContextHelper contextHelper)
-    {
-        HttpContext context = this.helperMap.remove(contextHelper);
-        if (context != null)
-        {
-            return removeHttpContext(context);
-        }
-        return null;
-    }
-
-    synchronized Map<String, HttpContextHolder> getHttpContexts()
-    {
-        return new HashMap<String, HttpContextHolder>(this.idMap);
-    }
-
-    synchronized Map<String, Set<AbstractMapping>> getOrphanMappings()
-    {
-        return new HashMap<String, Set<AbstractMapping>>(this.orphanMappings);
-    }
-
-    static class HttpContextHolder
-    {
-        private final HttpContext context;
-        private final Set<AbstractMapping> mappings;
-        private final String path;
-
-        HttpContextHolder(final HttpContext context)
-        {
-            this.context = context;
-            this.mappings = new HashSet<AbstractMapping>();
-            this.path = null;
-        }
-
-        HttpContextHolder(final ServletContextHelper context,
-                          final String path)
-        {
-            this.context = new HttpContextBridge(context);
-            this.mappings = new HashSet<AbstractMapping>();
-            this.path = path;
-        }
-
-        public HttpContext getContext()
-        {
-            return context;
-        }
-
-        public String getPath()
-        {
-            return path;
-        }
-
-        void addMapping(AbstractMapping mapping)
-        {
-            this.mappings.add(mapping);
-            mapping.setContext(getContext());
-        }
-
-        void removeMapping(AbstractMapping mapping)
-        {
-            mapping.setContext(null);
-            this.mappings.remove(mapping);
-        }
-
-        public Set<AbstractMapping> getMappings()
-        {
-            return mappings;
-        }
-    }
-}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/ServletContextHelperManager.java b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/ServletContextHelperManager.java
new file mode 100644
index 0000000..d51fbe9
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/ServletContextHelperManager.java
@@ -0,0 +1,150 @@
+/*
+ * 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.felix.http.base.internal.whiteboard;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.http.base.internal.runtime.ContextInfo;
+
+public final class ServletContextHelperManager
+{
+    private final Map<String, List<ContextHolder>> nameMap = new HashMap<String, List<ContextHolder>>();
+
+    /**
+     * Create a new servlet context helper manager
+     * and the default context
+     */
+    public ServletContextHelperManager()
+    {
+        // create default context
+        final ContextInfo info = new ContextInfo();
+        this.addContextHelper(info);
+    }
+
+    public void close()
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    private void activate(final ContextHolder holder)
+    {
+        // TODO
+    }
+
+    private void deactivate(final ContextHolder holder)
+    {
+     // TODO
+    }
+
+    /**
+     * Add a servlet context helper.
+     */
+    public void addContextHelper(final ContextInfo info)
+    {
+        final ContextHolder holder = new ContextHolder(info);
+        synchronized ( this.nameMap )
+        {
+            List<ContextHolder> holderList = this.nameMap.get(info.getName());
+            if ( holderList == null )
+            {
+                holderList = new ArrayList<ContextHolder>();
+                this.nameMap.put(info.getName(), holderList);
+            }
+            holderList.add(holder);
+            Collections.sort(holderList);
+            // check for activate/deactivate
+            if ( holderList.get(0) == holder )
+            {
+                // check for deactivate
+                if ( holderList.size() > 1 )
+                {
+                    this.deactivate(holderList.get(1));
+                }
+                this.activate(holder);
+            }
+        }
+    }
+
+    /**
+     * Remove a servlet context helper
+     */
+    public void removeContextHelper(final ContextInfo info)
+    {
+        synchronized ( this.nameMap )
+        {
+            final List<ContextHolder> holderList = this.nameMap.get(info.getName());
+            if ( holderList != null )
+            {
+                final Iterator<ContextHolder> i = holderList.iterator();
+                boolean first = true;
+                boolean activateNext = false;
+                while ( i.hasNext() )
+                {
+                    final ContextHolder holder = i.next();
+                    if ( holder.getInfo().compareTo(info) == 0 )
+                    {
+                        i.remove();
+                        // check for deactivate
+                        if ( first )
+                        {
+                            this.deactivate(holder);
+                            activateNext = true;
+                        }
+                        break;
+                    }
+                    first = false;
+                }
+                if ( holderList.isEmpty() )
+                {
+                    this.nameMap.remove(info.getName());
+                }
+                else if ( activateNext )
+                {
+                    this.activate(holderList.get(0));
+                }
+            }
+        }
+    }
+
+    private final static class ContextHolder implements Comparable<ContextHolder>
+    {
+        private final ContextInfo info;
+
+        public ContextHolder(final ContextInfo info)
+        {
+            this.info = info;
+        }
+
+        public ContextInfo getInfo()
+        {
+            return this.info;
+        }
+
+        @Override
+        public int compareTo(final ContextHolder o)
+        {
+            return this.info.compareTo(o.info);
+        }
+    }
+
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/tracker/AbstractTracker.java b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/tracker/AbstractTracker.java
index 62c3eea..c1707e6 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/tracker/AbstractTracker.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/tracker/AbstractTracker.java
@@ -23,11 +23,6 @@
 
 public abstract class AbstractTracker<T> extends ServiceTracker
 {
-    public AbstractTracker(final BundleContext context, final Class<T> clz)
-    {
-        super(context, clz.getName(), null);
-    }
-
     public AbstractTracker(final BundleContext context, final Filter filter)
     {
         super(context, filter, null);
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/tracker/ServletContextHelperTracker.java b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/tracker/ServletContextHelperTracker.java
index fd60d83..269e607 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/tracker/ServletContextHelperTracker.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/tracker/ServletContextHelperTracker.java
@@ -16,16 +16,23 @@
  */
 package org.apache.felix.http.base.internal.whiteboard.tracker;
 
-import org.apache.felix.http.base.internal.whiteboard.ExtenderManager;
+import org.apache.felix.http.base.internal.logger.SystemLogger;
+import org.apache.felix.http.base.internal.runtime.ContextInfo;
+import org.apache.felix.http.base.internal.whiteboard.ServletContextHelperManager;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.http.context.ServletContextHelper;
 import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
 
-public final class ServletContextHelperTracker extends AbstractTracker<ServletContextHelper>
+/**
+ * Tracks all {@link ServletContextHelper} services.
+ * Only services with the required properties are tracker, services missing these
+ * properties are ignored.
+ */
+public final class ServletContextHelperTracker extends AbstractReferenceTracker<ServletContextHelper>
 {
-    private final ExtenderManager manager;
+    private final ServletContextHelperManager contextManager;
 
     private static org.osgi.framework.Filter createFilter(final BundleContext btx)
     {
@@ -43,28 +50,34 @@
         return null; // we never get here - and if we get an NPE which is fine
     }
 
-    public ServletContextHelperTracker(final BundleContext context, ExtenderManager manager)
+    public ServletContextHelperTracker(final BundleContext context, final ServletContextHelperManager manager)
     {
         super(context, createFilter(context));
-        this.manager = manager;
+        this.contextManager = manager;
     }
 
     @Override
-    protected void added(ServletContextHelper service, ServiceReference ref)
+    protected void added(final ServiceReference<ServletContextHelper> ref)
     {
-        this.manager.add(service, ref);
+        final ContextInfo info = new ContextInfo(ref);
+
+        if ( info.isValid() )
+        {
+            this.contextManager.addContextHelper(info);
+        }
+        else
+        {
+            SystemLogger.debug("Ignoring ServletContextHelper service " + ref);
+        }
     }
 
     @Override
-    protected void modified(ServletContextHelper service, ServiceReference ref)
+    protected void removed(final ServiceReference<ServletContextHelper> ref)
     {
-        removed(service, ref);
-        added(service, ref);
-    }
-
-    @Override
-    protected void removed(ServletContextHelper service, ServiceReference ref)
-    {
-        this.manager.remove(service);
+        final ContextInfo info = new ContextInfo(ref);
+        if ( info.isValid() )
+        {
+            this.contextManager.removeContextHelper(info);
+        }
     }
 }