FELIX-4545 : Implement Servlet Context Helper

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1656000 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/AbstractInfo.java b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/AbstractInfo.java
index 0bb738a..ee74584 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/AbstractInfo.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/AbstractInfo.java
@@ -24,7 +24,10 @@
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
 
 /**
  * Base class for all info objects.
@@ -32,14 +35,19 @@
  */
 public abstract class AbstractInfo<T> implements Comparable<AbstractInfo<T>>
 {
+    /** Service id for services not provided through the service registry. */
+    private static final AtomicLong serviceIdCounter = new AtomicLong(-1);
+
     /** Service ranking. */
     private final int ranking;
 
     /** Service id. */
     private final long serviceId;
 
-    /** Service id for services not provided through the service registry. */
-    private static final AtomicLong serviceIdCounter = new AtomicLong(-1);
+    /** The context selection. */
+    private final String contextSelection;
+
+    private final Filter filter;
 
     /** Service reference. */
     private final ServiceReference<T> serviceReference;
@@ -57,13 +65,32 @@
             this.ranking = 0;
         }
         this.serviceReference = ref;
+        String sel = getStringProperty(ref, HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT);
+        if ( isEmpty(sel) )
+        {
+            this.contextSelection = "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "="
+                                   + HttpWhiteboardConstants.HTTP_WHITEBOARD_DEFAULT_CONTEXT_NAME + ")";
+        }
+        else
+        {
+            this.contextSelection = sel;
+        }
+        Filter f = null;
+        try
+        {
+            f = ref.getBundle().getBundleContext().createFilter(this.contextSelection);
+        }
+        catch ( final InvalidSyntaxException ise)
+        {
+            // ignore
+            f = null;
+        }
+        this.filter = f;
     }
 
     public AbstractInfo(final int ranking)
     {
-        this.ranking = ranking;
-        this.serviceId = serviceIdCounter.getAndDecrement();
-        this.serviceReference = null;
+        this(ranking, serviceIdCounter.getAndDecrement());
 
     }
 
@@ -72,6 +99,12 @@
         this.ranking = ranking;
         this.serviceId = serviceId;
         this.serviceReference = null;
+        this.contextSelection = null;
+        this.filter = null;
+    }
+
+    public boolean isValid() {
+        return this.filter != null || this.serviceReference == null;
     }
 
     /**
@@ -191,4 +224,42 @@
     {
         return this.serviceReference;
     }
+
+    public String getContextSelection()
+    {
+        return this.contextSelection;
+    }
+
+    public Filter getContextSelectionFilter()
+    {
+        return this.filter;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ranking;
+        result = prime * result + (int) (serviceId ^ (serviceId >>> 32));
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj)
+    {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        @SuppressWarnings("unchecked")
+        final AbstractInfo<T> other = (AbstractInfo<T>) obj;
+        if (ranking != other.ranking)
+            return false;
+        if (serviceId != other.serviceId)
+            return false;
+        return true;
+    }
 }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/ContextInfo.java b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/ContextInfo.java
index d10e7d5..5e046eb 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/ContextInfo.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/ContextInfo.java
@@ -37,20 +37,11 @@
         this.path = this.getStringProperty(ref, HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH);
     }
 
-    /**
-     * Create an info object for the default context.
-     */
-    public ContextInfo()
-    {
-        super(0, Long.MIN_VALUE);
-        this.name = HttpWhiteboardConstants.HTTP_WHITEBOARD_DEFAULT_CONTEXT_NAME;
-        this.path = "/";
-    }
-
+    @Override
     public boolean isValid()
     {
         // TODO - check if name and path are valid values
-        return !this.isEmpty(this.name) && !this.isEmpty(this.path);
+        return super.isValid() && !this.isEmpty(this.name) && !this.isEmpty(this.path);
     }
 
     public String getName()
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/ServletInfo.java b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/ServletInfo.java
index b352faf..12dfdb8 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/ServletInfo.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/ServletInfo.java
@@ -108,9 +108,10 @@
         this.context = context;
     }
 
+    @Override
     public boolean isValid()
     {
-        return  !isEmpty(this.patterns);
+        return super.isValid() && !isEmpty(this.patterns);
     }
 
     public String getName()
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 bc9e4c1..308685c 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
@@ -65,7 +65,7 @@
     public ExtenderManager(final HttpServiceImpl httpService, final BundleContext bundleContext)
     {
         this.mapping = new HashMap<String, AbstractMapping>();
-        this.contextManager = new ServletContextHelperManager(httpService);
+        this.contextManager = new ServletContextHelperManager(bundleContext, httpService);
         this.httpService = httpService;
         addTracker(new FilterTracker(bundleContext, this));
         addTracker(new ServletTracker(bundleContext, this.contextManager));
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
index bc35748..0c804b4 100644
--- 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
@@ -18,48 +18,102 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Dictionary;
 import java.util.HashMap;
+import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.felix.http.base.internal.runtime.AbstractInfo;
 import org.apache.felix.http.base.internal.runtime.ContextInfo;
 import org.apache.felix.http.base.internal.runtime.ServletInfo;
 import org.apache.felix.http.base.internal.service.HttpServiceImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.http.context.ServletContextHelper;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
 
 public final class ServletContextHelperManager
 {
-    private final Map<String, List<ContextHolder>> nameMap = new HashMap<String, List<ContextHolder>>();
+    /** A map containing all servlet context registrations. Mapped by context name */
+    private final Map<String, List<ContextHolder>> contextMap = new HashMap<String, List<ContextHolder>>();
+
+    /** A map with all servlet registrations, mapped by servlet info. */
+    private final Map<ServletInfo, List<ContextHolder>> servletList = new HashMap<ServletInfo, List<ContextHolder>>();
 
     private final HttpServiceImpl httpService;
 
+    private final ServiceRegistration<ServletContextHelper> defaultContextRegistration;
     /**
      * Create a new servlet context helper manager
      * and the default context
      */
-    public ServletContextHelperManager(final HttpServiceImpl httpService)
+    public ServletContextHelperManager(final BundleContext bundleContext, final HttpServiceImpl httpService)
     {
         this.httpService = httpService;
 
-        // create default context
-        final ContextInfo info = new ContextInfo();
-        this.addContextHelper(info);
+        final Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME, HttpWhiteboardConstants.HTTP_WHITEBOARD_DEFAULT_CONTEXT_NAME);
+        props.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH, "/");
+
+        this.defaultContextRegistration = bundleContext.registerService(ServletContextHelper.class,
+                new ServiceFactory<ServletContextHelper>() {
+
+                    @Override
+                    public ServletContextHelper getService(
+                            final Bundle bundle,
+                            final ServiceRegistration<ServletContextHelper> registration) {
+                        return new ServletContextHelper(bundle) {
+                        };
+                    }
+
+                    @Override
+                    public void ungetService(
+                            final Bundle bundle,
+                            final ServiceRegistration<ServletContextHelper> registration,
+                            final ServletContextHelper service) {
+                        // nothing to do
+                    }
+                },
+                props);
     }
 
     public void close()
     {
-        // TODO Auto-generated method stub
+        // TODO cleanup
 
+        this.defaultContextRegistration.unregister();
     }
 
     private void activate(final ContextHolder holder)
     {
-        // TODO
+        for(final Map.Entry<ServletInfo, List<ContextHolder>> entry : this.servletList.entrySet())
+        {
+            if ( entry.getKey().getContextSelectionFilter().match(holder.getInfo().getServiceReference()) )
+            {
+                entry.getValue().add(holder);
+                this.httpService.registerServlet(entry.getKey());
+            }
+        }
     }
 
     private void deactivate(final ContextHolder holder)
     {
-     // TODO
+        final Iterator<Map.Entry<ServletInfo, List<ContextHolder>>> i = this.servletList.entrySet().iterator();
+        while ( i.hasNext() )
+        {
+            final Map.Entry<ServletInfo, List<ContextHolder>> entry = i.next();
+            if ( entry.getValue().remove(holder) )
+            {
+                this.httpService.unregisterServlet(entry.getKey());
+                if ( entry.getValue().isEmpty() ) {
+                    i.remove();
+                }
+            }
+        }
     }
 
     /**
@@ -68,13 +122,13 @@
     public void addContextHelper(final ContextInfo info)
     {
         final ContextHolder holder = new ContextHolder(info);
-        synchronized ( this.nameMap )
+        synchronized ( this.contextMap )
         {
-            List<ContextHolder> holderList = this.nameMap.get(info.getName());
+            List<ContextHolder> holderList = this.contextMap.get(info.getName());
             if ( holderList == null )
             {
                 holderList = new ArrayList<ContextHolder>();
-                this.nameMap.put(info.getName(), holderList);
+                this.contextMap.put(info.getName(), holderList);
             }
             holderList.add(holder);
             Collections.sort(holderList);
@@ -96,9 +150,9 @@
      */
     public void removeContextHelper(final ContextInfo info)
     {
-        synchronized ( this.nameMap )
+        synchronized ( this.contextMap )
         {
-            final List<ContextHolder> holderList = this.nameMap.get(info.getName());
+            final List<ContextHolder> holderList = this.contextMap.get(info.getName());
             if ( holderList != null )
             {
                 final Iterator<ContextHolder> i = holderList.iterator();
@@ -122,7 +176,7 @@
                 }
                 if ( holderList.isEmpty() )
                 {
-                    this.nameMap.remove(info.getName());
+                    this.contextMap.remove(info.getName());
                 }
                 else if ( activateNext )
                 {
@@ -132,16 +186,58 @@
         }
     }
 
+    private List<ContextHolder> getMatchingContexts(final AbstractInfo<?> info)
+    {
+        final List<ContextHolder> result = new ArrayList<ContextHolder>();
+        for(final List<ContextHolder> holders : this.contextMap.values()) {
+            final ContextHolder h = holders.get(0);
+            if ( info.getContextSelectionFilter().match(h.getInfo().getServiceReference()) )
+            {
+                result.add(h);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Add a new servlet.
+     * @param servletInfo The servlet info
+     */
     public void addServlet(final ServletInfo servletInfo)
     {
-        this.httpService.registerServlet(servletInfo);
+        synchronized ( this.contextMap )
+        {
+            final List<ContextHolder> holderList = this.getMatchingContexts(servletInfo);
+            this.servletList.put(servletInfo, holderList);
+            for(final ContextHolder h : holderList)
+            {
+                this.httpService.registerServlet(servletInfo);
+            }
+        }
     }
 
+    /**
+     * Remove a servlet
+     * @param servletInfo The servlet info
+     */
     public void removeServlet(final ServletInfo servletInfo)
     {
-        this.httpService.unregisterServlet(servletInfo);
+        synchronized ( this.contextMap )
+        {
+            final List<ContextHolder> holderList = this.servletList.remove(servletInfo);
+            if ( holderList != null )
+            {
+                for(final ContextHolder h : holderList)
+                {
+                    this.httpService.unregisterServlet(servletInfo);
+                }
+            }
+        }
     }
 
+    /**
+     * Hold information about a context.
+     */
     private final static class ContextHolder implements Comparable<ContextHolder>
     {
         private final ContextInfo info;
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 269e607..f73ca2d 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
@@ -39,7 +39,7 @@
         try
         {
             return btx.createFilter(String.format("(&(objectClass=%s)(%s=*)(%s=*))",
-                    ServletContextHelperTracker.class.getName(),
+                    ServletContextHelper.class.getName(),
                     HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME,
                     HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH));
         }