make winegrower-servlet extensible + support OOTB asyncSupported and servletName attributes
diff --git a/winegrower-extension/winegrower-servlet/src/main/java/org/apache/winegrower/servlet/service/ServletHttpService.java b/winegrower-extension/winegrower-servlet/src/main/java/org/apache/winegrower/servlet/service/ServletHttpService.java
index 01edf2e..0b36eb0 100644
--- a/winegrower-extension/winegrower-servlet/src/main/java/org/apache/winegrower/servlet/service/ServletHttpService.java
+++ b/winegrower-extension/winegrower-servlet/src/main/java/org/apache/winegrower/servlet/service/ServletHttpService.java
@@ -37,7 +37,7 @@
 public class ServletHttpService implements HttpService {
     private final ServletContext context;
 
-    ServletHttpService(final ServletContext context) {
+    public /*let's reuse it*/ ServletHttpService(final ServletContext context) {
         this.context = context;
     }
 
@@ -46,10 +46,14 @@
     }
 
     public void registerFilter(final String alias, final Filter filter, final Dictionary initParams) {
-        final FilterRegistration.Dynamic dynamic = getContext(null).addFilter(alias, filter);
+        final Object filterName = initParams.get("filterName");
+        final FilterRegistration.Dynamic dynamic = getContext(null).addFilter(filterName == null ? alias : String.valueOf(filterName), filter);
+        final Object asyncSupported = initParams.get("asyncSupported");
+        dynamic.setAsyncSupported(asyncSupported == null || Boolean.parseBoolean(String.valueOf(asyncSupported)));
         if (initParams != null) {
-            list(initParams.keys())
-                .forEach(key -> dynamic.setInitParameter(String.valueOf(key), String.valueOf(initParams.get(key))));
+            list(initParams.keys()).stream()
+                    .filter(it -> !"filterName".equals(it) && !"asyncSupported".equals(it))
+                    .forEach(key -> dynamic.setInitParameter(String.valueOf(key), String.valueOf(initParams.get(key))));
         }
         dynamic.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), Boolean.parseBoolean(String.valueOf(initParams.get("is-match-after"))), alias);
     }
@@ -57,10 +61,14 @@
     @Override
     public void registerServlet(final String alias, final Servlet servlet,
                                 final Dictionary initParams, final HttpContext context) {
-        final ServletRegistration.Dynamic dynamic = getContext(context).addServlet(alias, servlet);
+        final Object servletName = initParams.get("servletName");
+        final ServletRegistration.Dynamic dynamic = getContext(context).addServlet(servletName == null ? alias : String.valueOf(servletName), servlet);
+        final Object asyncSupported = initParams.get("asyncSupported");
+        dynamic.setAsyncSupported(asyncSupported == null || Boolean.parseBoolean(String.valueOf(asyncSupported)));
         if (initParams != null) {
-            list(initParams.keys())
-                .forEach(key -> dynamic.setInitParameter(String.valueOf(key), String.valueOf(initParams.get(key))));
+            list(initParams.keys()).stream()
+                    .filter(it -> !"servletName".equals(it) && !"asyncSupported".equals(it))
+                    .forEach(key -> dynamic.setInitParameter(String.valueOf(key), String.valueOf(initParams.get(key))));
         }
         dynamic.addMapping(alias);
     }
diff --git a/winegrower-extension/winegrower-servlet/src/main/java/org/apache/winegrower/servlet/service/ServletHttpServiceDeployer.java b/winegrower-extension/winegrower-servlet/src/main/java/org/apache/winegrower/servlet/service/ServletHttpServiceDeployer.java
index 6d747e4..f57f5ab 100644
--- a/winegrower-extension/winegrower-servlet/src/main/java/org/apache/winegrower/servlet/service/ServletHttpServiceDeployer.java
+++ b/winegrower-extension/winegrower-servlet/src/main/java/org/apache/winegrower/servlet/service/ServletHttpServiceDeployer.java
@@ -13,22 +13,13 @@
  */
 package org.apache.winegrower.servlet.service;
 
-import static java.util.Arrays.asList;
-import static java.util.Optional.of;
-import static java.util.Optional.ofNullable;
-import static java.util.stream.Collectors.toList;
-
-import java.io.File;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.InvocationTargetException;
-import java.util.EventListener;
-import java.util.Hashtable;
-import java.util.Set;
-import java.util.function.BiConsumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.stream.Collector;
-import java.util.stream.Stream;
+import org.apache.winegrower.Ripener;
+import org.apache.winegrower.scanner.manifest.ManifestContributor;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.http.HttpService;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
 
 import javax.servlet.Filter;
 import javax.servlet.Servlet;
@@ -48,24 +39,34 @@
 import javax.servlet.http.HttpSessionBindingListener;
 import javax.servlet.http.HttpSessionIdListener;
 import javax.servlet.http.HttpSessionListener;
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.util.EventListener;
+import java.util.Hashtable;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collector;
+import java.util.stream.Stream;
 
-import org.apache.winegrower.Ripener;
-import org.apache.winegrower.scanner.manifest.ManifestContributor;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.http.HttpService;
-import org.osgi.util.tracker.ServiceTracker;
-import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import static java.util.Arrays.asList;
+import static java.util.Optional.of;
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toList;
 
 public class ServletHttpServiceDeployer implements ServletContainerInitializer {
 
     @Override
     public void onStartup(final Set<Class<?>> c, final ServletContext servletContext) {
-        if (is(servletContext, "winegrower.servlet.ripener.skip", false)) {
+        if (getClass() == ServletHttpServiceDeployer.class &&
+                is(servletContext, "winegrower.servlet.ripener.skip", false)) {
             return;
         }
         final Ripener.Configuration configuration = createConfiguration(servletContext);
         final Ripener.Impl ripener = new Ripener.Impl(configuration);
+        onRipenerCreated(ripener);
         if (is(servletContext, "winegrower.servlet.services.http.register", true)) {
             final ServletHttpService service = new ServletHttpService(servletContext);
             ripener.registerBuiltInService(HttpService.class, service, new Hashtable<>());
@@ -78,19 +79,39 @@
                     HttpSessionActivationListener.class, HttpSessionBindingListener.class,
                     HttpSessionIdListener.class,
                     ServletRequestListener.class, ServletRequestAttributeListener.class)
-                  .forEach(base -> registerListenerTracker(service, rootBundle, base));
+                    .forEach(base -> registerListenerTracker(service, rootBundle, base));
         }
         servletContext.setAttribute(Ripener.class.getName(), ripener.start());
+        onRipenerStarted(ripener);
         servletContext.addListener(new ServletContextListener() {
 
             @Override
             public void contextDestroyed(final ServletContextEvent sce) {
-                ofNullable(sce.getServletContext().getAttribute(Ripener.class.getName())).map(Ripener.class::cast)
+                ofNullable(sce.getServletContext().getAttribute(Ripener.class.getName()))
+                        .map(Ripener.class::cast)
+                        .map(it -> {
+                            onRipenerWillStop(it);
+                            return it;
+                        })
                         .ifPresent(Ripener::stop);
             }
         });
     }
 
+    // enables subclasses to register built in services
+    protected void onRipenerCreated(final Ripener ripener) {
+        // no-op
+    }
+
+    protected void onRipenerWillStop(final Ripener ripener) {
+        // no-op
+    }
+
+    // enables subclasses to wait for some start (OSGi-CDI typically)
+    protected void onRipenerStarted(final Ripener ripener) {
+        // no-op
+    }
+
     private Boolean is(final ServletContext servletContext, final String name, final boolean def) {
         return ofNullable(servletContext.getInitParameter(name)).map(String::valueOf).map(Boolean::parseBoolean).orElse(def);
     }
@@ -128,7 +149,7 @@
     }
 
     private <T extends Servlet> void registerServletTracker(final ServletHttpService service, final BundleContext bundleContext,
-            final Class<T> base) {
+                                                            final Class<T> base) {
         new StartupOnlyTracker<>(bundleContext, base, (reference, servlet) -> {
             final Hashtable<String, Object> props = toProps(reference);
             ofNullable(servlet.getClass().getAnnotation(WebServlet.class)).ifPresent(annotConfig -> {
@@ -144,7 +165,7 @@
     }
 
     private <T, A extends Annotation> Stream<String> findMapping(final ServiceReference<T> reference, final T servlet,
-            final Class<A> holder, final Function<A, String[]> mappingExtractor) {
+                                                                 final Class<A> holder, final Function<A, String[]> mappingExtractor) {
         return ofNullable(servlet.getClass().getAnnotation(holder)).map(mappingExtractor).filter(it -> it.length > 0)
                 .map(Stream::of).orElseGet(() -> ofNullable(reference.getProperty("alias")).map(String::valueOf).map(Stream::of)
                         .orElseGet(Stream::empty));
@@ -176,29 +197,29 @@
                 .ifPresent(configuration::setScanningExcludes);
         ofNullable(servletContext.getInitParameter("winegrower.servlet.ripener.configuration.manifestContributors"))
                 .map(String::valueOf).filter(it -> !it.isEmpty()).map(it -> asList(it.split(","))).ifPresent(contributors -> {
-                    configuration.setManifestContributors(contributors.stream().map(clazz -> {
-                        try {
-                            return Thread.currentThread().getContextClassLoader().loadClass(clazz).getConstructor().newInstance();
-                        } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException
-                                | ClassNotFoundException e) {
-                            throw new IllegalArgumentException(e);
-                        } catch (final InvocationTargetException e) {
-                            throw new IllegalArgumentException(e.getTargetException());
-                        }
-                    }).map(ManifestContributor.class::cast).collect(toList()));
-                });
+            configuration.setManifestContributors(contributors.stream().map(clazz -> {
+                try {
+                    return Thread.currentThread().getContextClassLoader().loadClass(clazz).getConstructor().newInstance();
+                } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException
+                        | ClassNotFoundException e) {
+                    throw new IllegalArgumentException(e);
+                } catch (final InvocationTargetException e) {
+                    throw new IllegalArgumentException(e.getTargetException());
+                }
+            }).map(ManifestContributor.class::cast).collect(toList()));
+        });
         ofNullable(servletContext.getInitParameter("winegrower.servlet.ripener.configuration.jarFilter")).map(String::valueOf)
                 .filter(it -> !it.isEmpty()).ifPresent(filter -> {
-                    try {
-                        configuration.setJarFilter((Predicate<String>) Thread.currentThread().getContextClassLoader()
-                                .loadClass(filter).getConstructor().newInstance());
-                    } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException
-                            | ClassNotFoundException e) {
-                        throw new IllegalArgumentException(e);
-                    } catch (final InvocationTargetException e) {
-                        throw new IllegalArgumentException(e.getTargetException());
-                    }
-                });
+            try {
+                configuration.setJarFilter((Predicate<String>) Thread.currentThread().getContextClassLoader()
+                        .loadClass(filter).getConstructor().newInstance());
+            } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException
+                    | ClassNotFoundException e) {
+                throw new IllegalArgumentException(e);
+            } catch (final InvocationTargetException e) {
+                throw new IllegalArgumentException(e.getTargetException());
+            }
+        });
         return configuration;
     }