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());
}
}