You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/10/18 23:25:36 UTC
[sling-org-apache-sling-startupfilter] 01/21: SLING-2347 - startup
filter module, rejects requests with a 503 status during startup
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 4d59e92c076d18fed60870b14b49e60e48b8e39e
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Fri Dec 30 16:55:12 2011 +0000
SLING-2347 - startup filter module, rejects requests with a 503 status during startup
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1225861 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 101 ++++++++++
.../apache/sling/startupfilter/StartupFilter.java | 64 ++++++
.../startupfilter/impl/StartupFilterImpl.java | 130 ++++++++++++
.../OSGI-INF/metatype/metatype.properties | 32 +++
.../startupfilter/impl/StartupFilterImplTest.java | 218 +++++++++++++++++++++
5 files changed, 545 insertions(+)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..ec1451f
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+ <!--
+ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>11</version>
+ <relativePath>../../../parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>org.apache.sling.startupfilter</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Sling Startup Filter</name>
+ <description>
+ Servlet Filter that blocks access to Sling
+ while starting up.
+ </description>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/startup-filter</connection>
+ <developerConnection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/startup-filter</developerConnection>
+ <url>http://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/startup-filter</url>
+ </scm>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.apache.sling.startupfilter</Export-Package>
+ <Private-Package>org.apache.sling.startupfilter.impl.*</Private-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jmock</groupId>
+ <artifactId>jmock-junit4</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/startupfilter/StartupFilter.java b/src/main/java/org/apache/sling/startupfilter/StartupFilter.java
new file mode 100644
index 0000000..4878e09
--- /dev/null
+++ b/src/main/java/org/apache/sling/startupfilter/StartupFilter.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+/** Servlet Filter that blocks access to the Sling main
+ * servlet during startup, by returning an HTTP 503
+ * or other suitable status code.
+ *
+ * A typical use case is to start this filter before
+ * the Sling main servlet (by setting a lower start level
+ * on its bundle than on the Sling engine bundle), and
+ * deactivating once startup is finished.
+ */
+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.
+ */
+ public static ProgressInfoProvider DEFAULT_INFO_PROVIDER = new ProgressInfoProvider() {
+ @Override
+ public String toString() {
+ return "Default ProgressInfoProvider";
+ }
+ public String getInfo() {
+ return DEFAULT_STATUS_MESSAGE;
+ }
+ };
+
+ /** Activate the supplied ProgressInfoProvider */
+ public void addProgressInfoProvider(ProgressInfoProvider pip);
+
+ /** 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);
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java b/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java
new file mode 100644
index 0000000..5baeb6a
--- /dev/null
+++ b/src/main/java/org/apache/sling/startupfilter/impl/StartupFilterImpl.java
@@ -0,0 +1,130 @@
+/*
+ * 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.impl;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Stack;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+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.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** StartupFilter implementation. Initially registered
+ * as a StartupFilter only, the Filter registration
+ * is dynamic, on-demand. */
+@Component(immediate=true, metatype=true)
+@Service(value=StartupFilter.class)
+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>();
+
+ @Property(boolValue=true)
+ public static final String DEFAULT_FILTER_ACTIVE_PROP = "default.filter.active";
+ private boolean defaultFilterActive;
+
+ /** @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);
+ }
+ }
+
+ /** @inheritDoc */
+ public void destroy() {
+ }
+
+ /** @inheritDoc */
+ public void init(FilterConfig cfg) throws ServletException {
+ }
+
+ @Activate
+ protected void activate(ComponentContext ctx) throws InterruptedException {
+ bundleContext = ctx.getBundleContext();
+ defaultFilterActive = (Boolean)ctx.getProperties().get(DEFAULT_FILTER_ACTIVE_PROP);
+ if(defaultFilterActive) {
+ addProgressInfoProvider(DEFAULT_INFO_PROVIDER);
+ }
+ log.info("Activated, defaultFilterActive={}", defaultFilterActive);
+ }
+
+ @Deactivate
+ protected void deactivate(ComponentContext ctx) throws InterruptedException {
+ unregisterFilter();
+ bundleContext = null;
+ }
+
+
+ /** @inheritDoc */
+ public synchronized void addProgressInfoProvider(ProgressInfoProvider pip) {
+ providers.push(pip);
+ log.info("Added {}", pip);
+ if(filterServiceRegistration == null) {
+ final Hashtable<String, String> params = new Hashtable<String, String>();
+ params.put("filter.scope", "REQUEST");
+ filterServiceRegistration = bundleContext.registerService(Filter.class.getName(), this, params);
+ log.info("Registered {} as a Filter service", this);
+ }
+ }
+
+ /** @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() {
+ if(filterServiceRegistration != null) {
+ filterServiceRegistration.unregister();
+ 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
new file mode 100644
index 0000000..7df6cfd
--- /dev/null
+++ b/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+
+
+#
+# This file contains localization strings for configuration labels and
+# descriptions as used in the metatype.xml descriptor generated by the
+# the Sling SCR plugin
+
+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 \
+ soon as the service starts.
diff --git a/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java b/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java
new file mode 100644
index 0000000..5cd85b5
--- /dev/null
+++ b/src/test/java/org/apache/sling/startupfilter/impl/StartupFilterImplTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.impl;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.startupfilter.StartupFilter;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.lib.action.DoAllAction;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+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;
+
+ TestPip(String s) {
+ info = s;
+ }
+
+ public String getInfo() {
+ return info;
+ }
+ };
+
+ static private class TestFilterImpl extends StartupFilterImpl {
+ void setup(ComponentContext ctx) throws Exception {
+ activate(ctx);
+ }
+ };
+
+ static private class ChangeInteger implements Action {
+ private final boolean increment;
+ private final AtomicInteger value;
+
+ ChangeInteger(AtomicInteger value, boolean increment) {
+ this.increment = increment;
+ this.value = value;
+ }
+ public void describeTo(Description d) {
+ d.appendText(increment ? "increment" : "decrement");
+ d.appendText(" an integer");
+ }
+ public Object invoke(Invocation invocation) throws Throwable {
+ if(increment) {
+ value.incrementAndGet();
+ } else {
+ value.decrementAndGet();
+ }
+ return null;
+ }
+ };
+
+ private TestFilterImpl filter;
+ private Mockery mockery;
+ 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();
+ 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();
+
+ final Action storeResponse = new Action() {
+ public void describeTo(Description d) {
+ d.appendText("Store HTTP response values");
+ }
+
+ public Object invoke(Invocation invocation) throws Throwable {
+ lastReturnedStatus = (Integer)invocation.getParameter(0);
+ lastReturnedMessage = (String)invocation.getParameter(1);
+ return null;
+ }
+ };
+
+ final Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put("default.filter.active", Boolean.TRUE);
+
+ mockery.checking(new Expectations() {{
+ allowing(componentContext).getBundleContext();
+ will(returnValue(bundleContext));
+
+ allowing(componentContext).getProperties();
+ will(returnValue(props));
+
+ 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);
+
+ allowing(serviceRegistration).unregister();
+ will(new ChangeInteger(activeFilterCount, false));
+ }});
+
+ filter.setup(componentContext);
+ }
+
+ 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);
+ }
+ }
+
+ @Test
+ public void testInitialState() throws Exception {
+ assertEquals("Initially expecting one filter service", 1, activeFilterCount.get());
+ assertRequest(503, StartupFilter.DEFAULT_STATUS_MESSAGE);
+ }
+
+ @Test
+ public void testDefaultFilterRemoved() throws Exception {
+ assertEquals("Initially expecting one filter service", 1, activeFilterCount.get());
+ filter.removeProgressInfoProvider(StartupFilter.DEFAULT_INFO_PROVIDER);
+ 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"),
+ };
+
+ 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);
+
+ // Now remove default and check
+ filter.removeProgressInfoProvider(StartupFilter.DEFAULT_INFO_PROVIDER);
+ assertRequest(0, null);
+ assertEquals("Expecting filter service to be gone", 0, activeFilterCount.get());
+ }
+}
--
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.