This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-startupfilter.git
commit ef6663bd3e2ddb374263f09e8c8e8c053b1fde4d Author: Bertrand Delacretaz <[email protected]> AuthorDate: Wed Jan 4 16:27:39 2012 +0000 SLING-2347 - use whiteboard pattern with StartupInfoProvider services git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1227210 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/sling/startupfilter/StartupFilter.java | 39 ++----- .../sling/startupfilter/StartupInfoProvider.java | 26 +++++ .../startupfilter/impl/StartupFilterImpl.java | 97 ++++++++++------ .../OSGI-INF/metatype/metatype.properties | 9 +- .../startupfilter/impl/StartupFilterImplTest.java | 127 +++++++++++---------- 5 files changed, 175 insertions(+), 123 deletions(-) diff --git a/src/main/java/org/apache/sling/startupfilter/StartupFilter.java b/src/main/java/org/apache/sling/startupfilter/StartupFilter.java index 4878e09..d84faec 100644 --- a/src/main/java/org/apache/sling/startupfilter/StartupFilter.java +++ b/src/main/java/org/apache/sling/startupfilter/StartupFilter.java @@ -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 diff --git a/src/main/java/org/apache/sling/startupfilter/StartupInfoProvider.java b/src/main/java/org/apache/sling/startupfilter/StartupInfoProvider.java new file mode 100644 index 0000000..e4baf85 --- /dev/null +++ b/src/main/java/org/apache/sling/startupfilter/StartupInfoProvider.java @@ -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(); +} diff --git a/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java b/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java index 5baeb6a..35f503e 100644 --- a/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java +++ b/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java @@ -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.Deactivate; 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 implements StartupFilter, Filter { 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 implements StartupFilter, Filter { /** @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 implements StartupFilter, Filter { } } - /** @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 diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties index 7df6cfd..2e04d0f 100644 --- a/src/main/resources/OSGI-INF/metatype/metatype.properties +++ b/src/main/resources/OSGI-INF/metatype/metatype.properties @@ -27,6 +27,11 @@ org.apache.sling.startupfilter.impl.StartupFilterImpl.name=Sling Startup Filter 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. diff --git a/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java b/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java index 5cd85b5..7d89f90 100644 --- a/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java +++ b/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java @@ -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()); } } -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
