You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2012/01/04 17:27:40 UTC

svn commit: r1227210 - in /sling/trunk/contrib/extensions/startup-filter/src: main/java/org/apache/sling/startupfilter/ main/java/org/apache/sling/startupfilter/impl/ main/resources/OSGI-INF/metatype/ test/java/org/apache/sling/startupfilter/impl/

Author: bdelacretaz
Date: Wed Jan  4 16:27:39 2012
New Revision: 1227210

URL: http://svn.apache.org/viewvc?rev=1227210&view=rev
Log:
SLING-2347 - use whiteboard pattern with StartupInfoProvider services

Added:
    sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/StartupInfoProvider.java
Modified:
    sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/StartupFilter.java
    sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java
    sling/trunk/contrib/extensions/startup-filter/src/main/resources/OSGI-INF/metatype/metatype.properties
    sling/trunk/contrib/extensions/startup-filter/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java

Modified: sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/StartupFilter.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/StartupFilter.java?rev=1227210&r1=1227209&r2=1227210&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/StartupFilter.java (original)
+++ sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/StartupFilter.java Wed Jan  4 16:27:39 2012
@@ -29,36 +29,17 @@ package org.apache.sling.startupfilter;
  */
 public interface StartupFilter {
     
-    String DEFAULT_STATUS_MESSAGE = "Startup in progress";
-    
-    /** Clients can supply objects implementing this
-     *  interface, to have the filter respond to HTTP
-     *  requests with the supplied information message.
-     */
-    public interface ProgressInfoProvider {
-        String getInfo();
-    }
-    
-    /** This ProgressInfoProvider is active by default, it 
-     *  must be removed for the filter to let requests pass through.
+    /** Enable the status filter, which outputs a default status message
+     *  and a concatenation of all status messages returned
+     *  by {@link StartupInfoProvider} services.
+     *  
+     *  The filter is initially enabled.   
      */
-    public static ProgressInfoProvider DEFAULT_INFO_PROVIDER = new ProgressInfoProvider() {
-        @Override
-        public String toString() {
-            return "Default ProgressInfoProvider";
-        }
-        public String getInfo() { 
-            return DEFAULT_STATUS_MESSAGE;
-        }
-    };
+    void enable();
     
-    /** Activate the supplied ProgressInfoProvider */
-    public void addProgressInfoProvider(ProgressInfoProvider pip);
+    /** Disable the status filter */
+    void disable();
     
-    /** Deactivate the supplied ProgressInfoProvider if it was
-     *  currently active.
-     *  Once all such providers are removed, the filter disables
-     *  itself and lets requests pass through.
-     */
-    public void removeProgressInfoProvider(ProgressInfoProvider pip);
+    /** True if currently enabled */
+    boolean isEnabled();
 }
\ No newline at end of file

Added: sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/StartupInfoProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/StartupInfoProvider.java?rev=1227210&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/StartupInfoProvider.java (added)
+++ sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/StartupInfoProvider.java Wed Jan  4 16:27:39 2012
@@ -0,0 +1,26 @@
+/*
+ * 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.startupfilter;
+
+public interface StartupInfoProvider {
+    /** Return startup progress information, which the startup
+     *  filter adds to its HTTP 503 response.  
+     */
+    String getProgressInfo();
+}

Modified: sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java?rev=1227210&r1=1227209&r2=1227210&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java (original)
+++ sling/trunk/contrib/extensions/startup-filter/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java Wed Jan  4 16:27:39 2012
@@ -19,8 +19,9 @@
 package org.apache.sling.startupfilter.impl;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Hashtable;
-import java.util.Stack;
+import java.util.List;
 
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -36,9 +37,12 @@ import org.apache.felix.scr.annotations.
 import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Service;
 import org.apache.sling.startupfilter.StartupFilter;
+import org.apache.sling.startupfilter.StartupInfoProvider;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.component.ComponentContext;
+import org.osgi.util.tracker.ServiceTracker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,25 +56,32 @@ public class StartupFilterImpl implement
     private final Logger log = LoggerFactory.getLogger(getClass());
     private ServiceRegistration filterServiceRegistration;
     private BundleContext bundleContext;
-    private final Stack<ProgressInfoProvider> providers = new Stack<ProgressInfoProvider>();
+    private ServiceTracker providersTracker;
+    private int providersTrackerCount = -1;
+    
+    private final List<StartupInfoProvider> providers = new ArrayList<StartupInfoProvider>();
     
     @Property(boolValue=true)
-    public static final String DEFAULT_FILTER_ACTIVE_PROP = "default.filter.active";
+    public static final String ACTIVE_BY_DEFAULT_PROP = "active.by.default";
     private boolean defaultFilterActive;
     
+    public static final String DEFAULT_MESSAGE = "Startup in progress";
+    
+    @Property(value=DEFAULT_MESSAGE)
+    public static final String DEFAULT_MESSAGE_PROP = "default.message";
+    private String defaultMessage;
+    
     /** @inheritDoc */
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
-        ProgressInfoProvider pip = null;
-        synchronized (this) {
-            if(!providers.isEmpty()) {
-                pip = providers.peek();
-            }
-        }
-        if(pip != null) {
-            ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, pip.getInfo());
-        } else {
-            chain.doFilter(request, response);
+        updateProviders();
+        
+        final StringBuilder sb = new StringBuilder();
+        sb.append(defaultMessage);
+        for(StartupInfoProvider p : providers) {
+            sb.append('\n');
+            sb.append(p.getProgressInfo());
         }
+        ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sb.toString());
     }
 
     /** @inheritDoc */
@@ -80,28 +91,54 @@ public class StartupFilterImpl implement
     /** @inheritDoc */
     public void init(FilterConfig cfg) throws ServletException {
     }
+    
+    /** If needed, update our list of providers */
+    private void updateProviders() {
+        if(providersTracker.getTrackingCount() != providersTrackerCount) {
+            synchronized(this) {
+                if(providersTracker.getTrackingCount() != providersTrackerCount) {
+                    providers.clear();
+                    final ServiceReference [] refs = providersTracker.getServiceReferences();
+                    if(refs != null) {
+                        for(ServiceReference ref : refs) {
+                            providers.add((StartupInfoProvider)bundleContext.getService(ref));
+                        }
+                    }
+                }
+                providersTrackerCount = providersTracker.getTrackingCount();
+                log.info("Reloaded list of StartupInfoProvider: {}", providers);
+            }
+        }
+    }
 
     @Activate
     protected void activate(ComponentContext ctx) throws InterruptedException {
         bundleContext = ctx.getBundleContext();
-        defaultFilterActive = (Boolean)ctx.getProperties().get(DEFAULT_FILTER_ACTIVE_PROP);
+        
+        providersTracker = new ServiceTracker(bundleContext, StartupInfoProvider.class.getName(), null);
+        providersTracker.open();
+        
+        Object prop = ctx.getProperties().get(DEFAULT_MESSAGE_PROP);
+        defaultMessage = prop == null ? DEFAULT_MESSAGE : prop.toString();
+                
+        prop = ctx.getProperties().get(ACTIVE_BY_DEFAULT_PROP);
+        defaultFilterActive = (prop instanceof Boolean ? (Boolean)prop : false);
         if(defaultFilterActive) {
-            addProgressInfoProvider(DEFAULT_INFO_PROVIDER);
+            enable();
         }
-        log.info("Activated, defaultFilterActive={}", defaultFilterActive);
+        log.info("Activated, enabled={}", isEnabled());
     }
     
     @Deactivate
     protected void deactivate(ComponentContext ctx) throws InterruptedException {
-        unregisterFilter();
+        disable();
+        providersTracker.close();
+        providersTracker = null;
         bundleContext = null;
     }
     
     
-    /** @inheritDoc */
-    public synchronized void addProgressInfoProvider(ProgressInfoProvider pip) {
-        providers.push(pip);
-        log.info("Added {}", pip);
+    public synchronized void enable() {
         if(filterServiceRegistration == null) {
             final Hashtable<String, String> params = new Hashtable<String, String>();
             params.put("filter.scope", "REQUEST");
@@ -110,21 +147,15 @@ public class StartupFilterImpl implement
         }
     }
     
-    /** @inheritDoc */
-    public synchronized void removeProgressInfoProvider(ProgressInfoProvider pip) {
-        providers.remove(pip);
-        log.info("Removed {}", pip);
-        if(providers.isEmpty()) {
-            log.info("No more ProgressInfoProviders, unregistering Filter service");
-            unregisterFilter();
-        }
-    }
-    
-    private synchronized void unregisterFilter() {
+    public synchronized void disable() {
         if(filterServiceRegistration != null) {
             filterServiceRegistration.unregister();
             filterServiceRegistration = null;
+            log.info("Filter service disabled");
         }
     }
-     
+    
+    public synchronized boolean isEnabled() {
+        return filterServiceRegistration != null;
+    }
 }
\ No newline at end of file

Modified: sling/trunk/contrib/extensions/startup-filter/src/main/resources/OSGI-INF/metatype/metatype.properties
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/startup-filter/src/main/resources/OSGI-INF/metatype/metatype.properties?rev=1227210&r1=1227209&r2=1227210&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/startup-filter/src/main/resources/OSGI-INF/metatype/metatype.properties (original)
+++ sling/trunk/contrib/extensions/startup-filter/src/main/resources/OSGI-INF/metatype/metatype.properties Wed Jan  4 16:27:39 2012
@@ -27,6 +27,11 @@ org.apache.sling.startupfilter.impl.Star
 org.apache.sling.startupfilter.impl.StartupFilterImpl.description=Rejects Sling requests \
   with a 503 error code during startup.
 
-default.filter.active.name=Default filter active?
-default.filter.active.description=If true, the filter is active as \
+active.by.default.name=Active by default?
+active.by.default.description=If true, the filter is active as \
   soon as the service starts.
+  
+default.message.name=Default message
+default.message.description=The default message is returned in the \
+  HTTP response of the filter, followed by any messages supplied \
+  by StartupInfoProvider services.

Modified: sling/trunk/contrib/extensions/startup-filter/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/startup-filter/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java?rev=1227210&r1=1227209&r2=1227210&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/startup-filter/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java (original)
+++ sling/trunk/contrib/extensions/startup-filter/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java Wed Jan  4 16:27:39 2012
@@ -29,7 +29,7 @@ import javax.servlet.FilterChain;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.sling.startupfilter.StartupFilter;
+import org.apache.sling.startupfilter.StartupInfoProvider;
 import org.hamcrest.Description;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
@@ -38,24 +38,50 @@ import org.jmock.api.Invocation;
 import org.jmock.lib.action.DoAllAction;
 import org.junit.Before;
 import org.junit.Test;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.component.ComponentContext;
 
 /** Test the StartupFilterImpl */
 public class StartupFilterImplTest {
-    static private class TestPip implements StartupFilter.ProgressInfoProvider {
-        String info;
+    static private class TestProvider implements StartupInfoProvider, ServiceReference {
+        private final String info;
         
-        TestPip(String s) {
+        TestProvider(String s) {
             info = s;
         }
         
-        public String getInfo() {
+        public String getProgressInfo() {
             return info;
         }
-    };
-    
+
+        public Object getProperty(String key) {
+            return null;
+        }
+
+        public String[] getPropertyKeys() {
+            return null;
+        }
+
+        public Bundle getBundle() {
+            return null;
+        }
+
+        public Bundle[] getUsingBundles() {
+            return null;
+        }
+
+        public boolean isAssignableTo(Bundle bundle, String className) {
+            return false;
+        }
+
+        public int compareTo(Object reference) {
+            return 0;
+        }
+    }
     static private class TestFilterImpl extends StartupFilterImpl {
         void setup(ComponentContext ctx) throws Exception {
             activate(ctx);
@@ -89,24 +115,25 @@ public class StartupFilterImplTest {
     private HttpServletRequest request; 
     private HttpServletResponse response;
     private FilterChain chain;
-    private AtomicInteger doChainCount;
     private int lastReturnedStatus;
     private String lastReturnedMessage;
     private AtomicInteger activeFilterCount;
     private ServiceRegistration serviceRegistration;
 
     @Before
-    public void setup() throws Exception {
-        doChainCount = new AtomicInteger();
+    public void setup() {
         activeFilterCount = new AtomicInteger();
         mockery = new Mockery();
-        final BundleContext bundleContext = mockery.mock(BundleContext.class); 
-        final ComponentContext componentContext = mockery.mock(ComponentContext.class); 
         request = mockery.mock(HttpServletRequest.class); 
         response = mockery.mock(HttpServletResponse.class);
         chain = mockery.mock(FilterChain.class);
         serviceRegistration = mockery.mock(ServiceRegistration.class);
         filter = new TestFilterImpl();
+    }
+    
+    private void setProvider(final TestProvider provider) throws Exception {
+        final BundleContext bundleContext = mockery.mock(BundleContext.class); 
+        final ComponentContext componentContext = mockery.mock(ComponentContext.class); 
         
         final Action storeResponse = new Action() {
             public void describeTo(Description d) {
@@ -121,8 +148,9 @@ public class StartupFilterImplTest {
         };
         
         final Dictionary<String, Object> props = new Hashtable<String, Object>();
-        props.put("default.filter.active", Boolean.TRUE);
+        props.put(StartupFilterImpl.ACTIVE_BY_DEFAULT_PROP, Boolean.TRUE);
         
+        final ServiceReference [] providerRefs = provider == null ? null : new ServiceReference[] { provider };
         mockery.checking(new Expectations() {{
             allowing(componentContext).getBundleContext();
             will(returnValue(bundleContext));
@@ -130,15 +158,21 @@ public class StartupFilterImplTest {
             allowing(componentContext).getProperties();
             will(returnValue(props));
             
+            allowing(bundleContext).createFilter(with(any(String.class)));
+            allowing(bundleContext).addServiceListener(with(any(ServiceListener.class)));
+            allowing(bundleContext).addServiceListener(with(any(ServiceListener.class)), with(any(String.class)));
+            
+            allowing(bundleContext).getServiceReferences(StartupInfoProvider.class.getName(), null);
+            will(returnValue(providerRefs));
+            allowing(bundleContext).getService(with(any(ServiceReference.class)));
+            will(returnValue(provider));
+            
             allowing(bundleContext).registerService(with(Filter.class.getName()), with(any(Object.class)), with(any(Dictionary.class)));
             will(new DoAllAction(
                     new ChangeInteger(activeFilterCount, true),
                     returnValue(serviceRegistration)
                     ));
             
-            allowing(chain).doFilter(request, response);
-            will(new ChangeInteger(doChainCount, true));
-            
             allowing(response).sendError(with(any(Integer.class)), with(any(String.class)));
             will(storeResponse);
             
@@ -152,67 +186,42 @@ public class StartupFilterImplTest {
     private void assertRequest(final int expectedStatus, final String expectedMessage) throws Exception {
         lastReturnedMessage = null;
         lastReturnedStatus = -1;
-        final int oldDoChainCount = doChainCount.get();
         
         filter.doFilter(request, response, chain);
         
         // status 0 means we expect the request to go through
-        if(expectedStatus == 0) {
-            assertEquals("Expecting doChain to have been be called once", 
-                    1, doChainCount.get() - oldDoChainCount);
-        } else {
-            assertEquals("Expecting status to match", 
-                    expectedStatus, lastReturnedStatus);
-            assertEquals("Expecting message to match", 
-                    expectedMessage, lastReturnedMessage);
-        }
+        assertEquals("Expecting status to match", 
+                expectedStatus, lastReturnedStatus);
+        assertEquals("Expecting message to match", 
+                expectedMessage, lastReturnedMessage);
     }
     
     @Test
     public void testInitialState() throws Exception {
-        assertEquals("Initially expecting one filter service", 1, activeFilterCount.get());
-        assertRequest(503, StartupFilter.DEFAULT_STATUS_MESSAGE);
+        setProvider(null);
+        assertEquals("Initially expecting the default status message", 1, activeFilterCount.get());
+        assertRequest(503, StartupFilterImpl.DEFAULT_MESSAGE);
     }
-    
+
     @Test
-    public void testDefaultFilterRemoved() throws Exception {
+    public void testDisabling() throws Exception {
+        setProvider(null);
         assertEquals("Initially expecting one filter service", 1, activeFilterCount.get());
-        filter.removeProgressInfoProvider(StartupFilter.DEFAULT_INFO_PROVIDER);
+        filter.disable();
         assertEquals("Expecting filter service to be gone", 0, activeFilterCount.get());
-        assertRequest(0, null);
     }
-    
+
     @Test
-    public void testSeveralProviders() throws Exception {
-        final StartupFilter.ProgressInfoProvider [] pips = {
-                new TestPip("one"),
-                new TestPip("two"),
-                new TestPip("three"),
-        };
+    public void testProviders() throws Exception {
+        final TestProvider p = new TestProvider("TEST");
         
+        setProvider(p);
         assertEquals("Initially expecting one filter service", 1, activeFilterCount.get());
         
-        // Last added provider must be active
-        for(StartupFilter.ProgressInfoProvider pip : pips) {
-            filter.addProgressInfoProvider(pip);
-            assertRequest(503, pip.getInfo());
-        }
-        
-        assertEquals("After adding several providers, expecting one filter service", 1, activeFilterCount.get());
-        
-        // When removing a provider the previous one becomes active
-        for(int i = pips.length - 1; i >= 0; i--) {
-            assertRequest(503, pips[i].getInfo());
-            filter.removeProgressInfoProvider(pips[i]);
-        }
-
-        // After removing all, default is active again
-        assertEquals("After removing providers, expecting one filter service", 1, activeFilterCount.get());
-        assertRequest(503, StartupFilter.DEFAULT_STATUS_MESSAGE);
+        final String expectedMessage = StartupFilterImpl.DEFAULT_MESSAGE + "\nTEST";
+        assertRequest(503, expectedMessage);
 
-        // Now remove default and check
-        filter.removeProgressInfoProvider(StartupFilter.DEFAULT_INFO_PROVIDER);
-        assertRequest(0, null);
+        filter.disable();
         assertEquals("Expecting filter service to be gone", 0, activeFilterCount.get());
     }
 }