You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2020/01/10 15:22:35 UTC

[isis] 21/21: Merge remote-tracking branch 'origin/master' into ide

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-2259
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 868a69c055362ff830e4a02444bad00955ab4a30
Merge: 31b8b21 ba33cc2
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Jan 10 15:21:34 2020 +0000

    Merge remote-tracking branch 'origin/master' into ide
    
    # Conflicts:
    #	core/config/src/main/java/org/apache/isis/config/viewer/wicket/WebAppContextPath.java
    #	core/security/shiro/src/main/java/org/apache/isis/security/shiro/webmodule/WebModuleShiro.java
    #	core/viewers/restfulobjects/jaxrs-resteasy-4/src/main/java/org/apache/isis/viewer/restfulobjects/jaxrsresteasy4/webmodule/WebModuleJaxrsResteasy4.java
    #	core/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/webmodule/IsisRestfulObjectsSessionFilter.java
    #	core/webapp/src/main/java/org/apache/isis/webapp/modules/templresources/TemplateResourceServlet.java
    #	valuetypes/sse/src/main/java/org/apache/isis/extensions/sse/webmodule/ServerSentEventsServlet.java

 .../isis/core/config/viewer/wicket/WebAppContextPath.java |  2 ++
 .../modules/templresources/TemplateResourceServlet.java   |  9 ++++-----
 .../webapp/webappctx/IsisWebAppContextInitializer.java    | 15 ++++++++++++++-
 .../runtime/jee_support/IsisCDIBeanScanInterceptor.java   |  2 ++
 .../isis/security/shiro/webmodule/WebModuleShiro.java     | 10 ++++++++++
 .../sse/ui/webmodule/ServerSentEventsServlet.java         |  6 +++---
 .../jaxrsresteasy4/webmodule/WebModuleJaxrsResteasy4.java | 14 ++++++++++++++
 .../viewer/webmodule/IsisRestfulObjectsSessionFilter.java |  8 ++++----
 .../webmodule/IsisTransactionFilterForRestfulObjects.java |  6 +++---
 9 files changed, 56 insertions(+), 16 deletions(-)

diff --cc core/config/src/main/java/org/apache/isis/core/config/viewer/wicket/WebAppContextPath.java
index 6130055,0000000..de8f391
mode 100644,000000..100644
--- a/core/config/src/main/java/org/apache/isis/core/config/viewer/wicket/WebAppContextPath.java
+++ b/core/config/src/main/java/org/apache/isis/core/config/viewer/wicket/WebAppContextPath.java
@@@ -1,100 -1,0 +1,102 @@@
 +/*
 + *  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.isis.core.config.viewer.wicket;
 +
 +import java.io.Serializable;
 +import java.util.regex.Matcher;
 +import java.util.regex.Pattern;
 +
 +import javax.inject.Named;
 +import javax.inject.Singleton;
 +
 +import org.springframework.beans.factory.annotation.Qualifier;
 +import org.springframework.context.annotation.Primary;
 +import org.springframework.core.annotation.Order;
 +import org.springframework.stereotype.Service;
 +
 +import org.apache.isis.applib.annotation.OrderPrecedence;
 +import org.apache.isis.core.commons.internal.base._Strings;
 +import org.apache.isis.core.commons.internal.resources._Resources;
 +
 +import lombok.Getter;
 +
 +/**
 + * This class is {@link Serializable} so that it can be injected into Wicket components.
 + */
 +@Service
 +@Singleton
 +@Named("isisConfig.WebAppContextPath")
 +@Order(OrderPrecedence.MIDPOINT)
 +@Primary
 +@Qualifier("Default")
 +public class WebAppContextPath implements Serializable {
 +
++    private static final long serialVersionUID = 1L;
++
 +    /**
 +     * In the form "xxx/yyy" (no leading nor trailing '/').
 +     */
 +    @Getter
 +    private String contextPath = "";
 +    private static final Pattern pattern = Pattern.compile("^[/]*(.+?)[/]*$");;
 +
 +    public void setContextPath(final String contextPath) {
 +        if(contextPath == null) {
 +            this.contextPath = "";
 +            return;
 +        }
 +        final Matcher matcher = pattern.matcher(contextPath);
 +        this.contextPath = matcher.matches()
 +                                ? matcher.group(1)
 +                                : "";
 +    }
 +
 +    public final String prependContextPath(String path) {
 +
 +        if(path==null) {
 +            return getContextPath();
 +        }
 +
 +        final String contextPath = getContextPath();
 +        if(_Strings.isNullOrEmpty(contextPath)) {
 +            return path;
 +        }
 +
 +        return contextPath + prefixed(path, "/");
 +    }
 +
 +    private static String prefixed(final String path, final String prefix) {
 +        return !path.startsWith(prefix)
 +                ? prefix + path
 +                : path;
 +    }
 +
 +    public String prependContextPathIfLocal(String url) {
 +
 +        if(url==null) {
 +            return null;
 +        }
 +        if(_Resources.isLocalResource(url)) {
 +            return this.prependContextPath(url);
 +        }
 +        return url;
 +    }
 +
 +
 +}
diff --cc core/webapp/src/main/java/org/apache/isis/webapp/modules/templresources/TemplateResourceServlet.java
index b6f1c92,0a35c02..e79aaa3
--- a/core/webapp/src/main/java/org/apache/isis/webapp/modules/templresources/TemplateResourceServlet.java
+++ b/core/webapp/src/main/java/org/apache/isis/webapp/modules/templresources/TemplateResourceServlet.java
@@@ -30,16 -29,18 +29,18 @@@ import javax.servlet.http.HttpServlet
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
+ import org.springframework.beans.factory.annotation.Autowired;
+ 
 -import org.apache.isis.commons.internal.base._Bytes;
 -import org.apache.isis.commons.internal.base._Strings;
 -import org.apache.isis.config.RestEasyConfiguration;
 -import org.apache.isis.config.viewer.wicket.WebAppContextPath;
 -import org.apache.isis.metamodel.commons.InputStreamExtensions;
 -import org.apache.isis.metamodel.commons.ResourceUtil;
 -import org.apache.isis.metamodel.commons.StringExtensions;
 -
 -import static org.apache.isis.commons.internal.base._Strings.pair;
 -import static org.apache.isis.commons.internal.base._With.ifPresentElseGet;
 +import org.apache.isis.core.commons.internal.base._Bytes;
 +import org.apache.isis.core.commons.internal.base._Strings;
 +import org.apache.isis.core.config.RestEasyConfiguration;
 +import org.apache.isis.core.config.viewer.wicket.WebAppContextPath;
 +import org.apache.isis.core.metamodel.commons.InputStreamExtensions;
 +import org.apache.isis.core.metamodel.commons.ResourceUtil;
 +import org.apache.isis.core.metamodel.commons.StringExtensions;
 +
 +import static org.apache.isis.core.commons.internal.base._Strings.pair;
 +import static org.apache.isis.core.commons.internal.base._With.ifPresentElseGet;
  
  import lombok.val;
  import lombok.extern.log4j.Log4j2;
diff --cc core/webapp/src/main/java/org/apache/isis/webapp/webappctx/IsisWebAppContextInitializer.java
index 7d85cc7,2a9507b..ce8a93b
--- a/core/webapp/src/main/java/org/apache/isis/webapp/webappctx/IsisWebAppContextInitializer.java
+++ b/core/webapp/src/main/java/org/apache/isis/webapp/webappctx/IsisWebAppContextInitializer.java
@@@ -27,11 -28,12 +28,12 @@@ import javax.servlet.ServletContextList
  import javax.servlet.ServletException;
  
  import org.springframework.boot.web.servlet.ServletContextInitializer;
+ import org.springframework.stereotype.Component;
  
  import org.apache.isis.applib.services.registry.ServiceRegistry;
 -import org.apache.isis.commons.internal.context._Context;
 -import org.apache.isis.config.IsisConfiguration;
 -import org.apache.isis.config.viewer.wicket.WebAppContextPath;
 +import org.apache.isis.core.commons.internal.context._Context;
 +import org.apache.isis.core.config.IsisConfiguration;
 +import org.apache.isis.core.config.viewer.wicket.WebAppContextPath;
  import org.apache.isis.webapp.modules.WebModule;
  import org.apache.isis.webapp.modules.WebModuleContext;
  
diff --cc security/shiro/src/main/java/org/apache/isis/security/shiro/webmodule/WebModuleShiro.java
index 35ab433,0000000..c194969
mode 100644,000000..100644
--- a/security/shiro/src/main/java/org/apache/isis/security/shiro/webmodule/WebModuleShiro.java
+++ b/security/shiro/src/main/java/org/apache/isis/security/shiro/webmodule/WebModuleShiro.java
@@@ -1,192 -1,0 +1,202 @@@
 +/*
 + *  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.isis.security.shiro.webmodule;
 +
 +import java.util.Collection;
 +import java.util.HashMap;
 +import java.util.Map;
 +
 +import javax.inject.Inject;
 +import javax.inject.Named;
 +import javax.servlet.ServletContext;
++import javax.servlet.ServletContextEvent;
 +import javax.servlet.ServletContextListener;
 +import javax.servlet.ServletException;
 +
 +import org.apache.shiro.config.Ini;
 +import org.apache.shiro.realm.Realm;
 +import org.apache.shiro.web.env.EnvironmentLoaderListener;
 +import org.apache.shiro.web.env.IniWebEnvironment;
 +import org.apache.shiro.web.env.WebEnvironment;
 +import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
 +import org.apache.shiro.web.servlet.ShiroFilter;
 +import org.springframework.beans.factory.annotation.Qualifier;
 +import org.springframework.core.annotation.Order;
 +import org.springframework.stereotype.Service;
 +import org.springframework.util.ReflectionUtils;
 +
 +import org.apache.isis.applib.annotation.OrderPrecedence;
 +import org.apache.isis.applib.services.inject.ServiceInjector;
 +import org.apache.isis.core.commons.collections.Can;
 +import org.apache.isis.core.commons.internal._Constants;
 +import org.apache.isis.core.commons.internal.base._Strings;
++import org.apache.isis.core.commons.internal.exceptions._Exceptions;
 +import org.apache.isis.webapp.modules.WebModuleAbstract;
 +import org.apache.isis.webapp.modules.WebModuleContext;
 +
 +import lombok.Getter;
 +import lombok.NoArgsConstructor;
 +import lombok.SneakyThrows;
 +import lombok.val;
 +
 +/**
 + * WebModule to enable support for Shiro.
 + * <p>
 + * Can be customized via static {@link WebModuleShiro#setShiroEnvironmentClass(Class)}
 + * @since 2.0
 + */
 +@Service
 +@Named("isisSecurityKeycloak.WebModuleKeycloak")
 +@Order(OrderPrecedence.HIGHEST + 200)
 +@Qualifier("Shiro")
 +public class WebModuleShiro extends WebModuleAbstract {
 +    
 +
 +    private final static String SHIRO_FILTER_NAME = "ShiroFilter";
 +
 +    @Inject
 +    public WebModuleShiro(final ServiceInjector serviceInjector) {
 +        super(serviceInjector);
 +    }
 +
 +    // -- CONFIGURATION
 +
 +    public static void setShiroEnvironmentClass(Class<? extends WebEnvironment> shiroEnvironmentClass) {
 +        if(shiroEnvironmentClass==null) {
 +            System.setProperty("shiroEnvironmentClass", null);
 +            return;
 +        } 
 +        System.setProperty("shiroEnvironmentClass", shiroEnvironmentClass.getName());
 +    }
 +
 +    public static class IniWebEnvironmentUsingSystemProperty extends IniWebEnvironment {
 +        @Override
 +        public Ini getIni() {
 +            val customShiroIniResource = System.getProperty("shiroIniResource");
 +            if(_Strings.isNotEmpty(customShiroIniResource)) {
 +                val ini = new Ini();
 +                ini.loadFromPath(customShiroIniResource);
 +                return ini;	
 +            } 
 +            return null;
 +        }
 +        // see https://issues.apache.org/jira/browse/SHIRO-610
 +        @Override
 +        protected Map<String, Object> getDefaults() {
 +            Map<String, Object> defaults = new HashMap<String, Object>();
 +            defaults.put(FILTER_CHAIN_RESOLVER_NAME, new PathMatchingFilterChainResolver());
 +            return defaults;
 +        }
 +    }
 +
 +    public static void setShiroIniResource(String resourcePath) {
 +        if(resourcePath==null) {
 +            System.setProperty("shiroIniResource", null);
 +            setShiroEnvironmentClass(null);
 +            return;
 +        }
 +        System.setProperty("shiroIniResource", resourcePath);
 +        setShiroEnvironmentClass(IniWebEnvironmentUsingSystemProperty.class);
 +    }
 +    
 +    /**
 +     * Adds support for dependency injection into security realms
 +     * @since 2.0
 +     */
 +    @NoArgsConstructor // don't remove, this is class is managed by Isis
 +    public static class EnvironmentLoaderListenerForIsis extends EnvironmentLoaderListener {
 +
 +        @Inject private ServiceInjector serviceInjector;
 +
 +        // testing support
 +        public EnvironmentLoaderListenerForIsis(ServiceInjector serviceInjector) {
 +            this.serviceInjector = serviceInjector;
 +        }
 +
++        @Override
++        public void contextInitialized(ServletContextEvent sce) {
++            System.err.println("intercepted: " + this);
++            _Exceptions.dumpStackTrace();
++            System.err.println("======================================================");
++            super.contextInitialized(sce);
++        }
++
 +        @Override 
 +        protected WebEnvironment createEnvironment(ServletContext servletContext) {
 +            val shiroEnvironment = super.createEnvironment(servletContext);
 +            val securityManager = shiroEnvironment.getSecurityManager();
 +
 +            injectServicesIntoRealms(securityManager);
 +            
 +            return shiroEnvironment;
 +        }
 +        
 +        @SuppressWarnings("unchecked")
 +        @SneakyThrows
 +        public void injectServicesIntoRealms(
 +                org.apache.shiro.mgt.SecurityManager securityManager) {
 +
 +            // reflective access to SecurityManager.getRealms()
 +            val realms = (Collection<Realm>) ReflectionUtils
 +                    .findMethod(securityManager.getClass(), "getRealms")
 +                    .invoke(securityManager, _Constants.emptyObjects);
 +
 +            realms.stream().forEach(serviceInjector::injectServicesInto);
 +        }
 +        
 +    }
 +
 +    // -- 
 +
 +    @Getter
 +    private final String name = "Shiro";
 +
 +    @Override
 +    public void prepare(final WebModuleContext ctx) {
 +        super.prepare(ctx);
 +        val customShiroEnvironmentClassName = System.getProperty("shiroEnvironmentClass");
 +        if(_Strings.isEmpty(customShiroEnvironmentClassName)) {
 +            setShiroEnvironmentClass(IniWebEnvironmentUsingSystemProperty.class);
 +        }
 +    }
 +
 +    @Override
 +    public Can<ServletContextListener> init(ServletContext ctx) throws ServletException {
 +
 +        registerFilter(ctx, SHIRO_FILTER_NAME, ShiroFilter.class)
 +            .ifPresent(filterReg -> {
 +                filterReg.addMappingForUrlPatterns(
 +                        null,
 +                        false, // filter is forced first
 +                        "/*");
 +            });
 +
 +        val customShiroEnvironmentClassName = System.getProperty("shiroEnvironmentClass");
 +        if(_Strings.isNotEmpty(customShiroEnvironmentClassName)) {
 +            ctx.setInitParameter("shiroEnvironmentClass", customShiroEnvironmentClassName);
 +        }
 +
 +        val listener = createListener(EnvironmentLoaderListenerForIsis.class);
 +        return Can.ofSingleton(listener);
 +
 +    }
 +
 +
 +
 +}
diff --cc valuetypes/sse/ui/src/main/java/org/apache/isis/extensions/sse/ui/webmodule/ServerSentEventsServlet.java
index 7a706e3,0000000..a4484a0
mode 100644,000000..100644
--- a/valuetypes/sse/ui/src/main/java/org/apache/isis/extensions/sse/ui/webmodule/ServerSentEventsServlet.java
+++ b/valuetypes/sse/ui/src/main/java/org/apache/isis/extensions/sse/ui/webmodule/ServerSentEventsServlet.java
@@@ -1,165 -1,0 +1,165 @@@
 +/*
 + *  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.isis.extensions.sse.ui.webmodule;
 +
 +import java.io.IOException;
 +import java.util.Optional;
 +import java.util.concurrent.ExecutorService;
 +import java.util.concurrent.ForkJoinPool;
 +
- import javax.inject.Inject;
 +import javax.servlet.AsyncContext;
 +import javax.servlet.ServletException;
 +import javax.servlet.http.HttpServlet;
 +import javax.servlet.http.HttpServletRequest;
 +import javax.servlet.http.HttpServletResponse;
 +
++import org.springframework.beans.factory.annotation.Autowired;
++
 +import org.apache.isis.applib.util.JaxbAdapters;
 +import org.apache.isis.core.commons.internal.base._Strings;
 +import org.apache.isis.core.commons.internal.context._Context;
 +import org.apache.isis.extensions.sse.applib.service.SseChannel;
 +import org.apache.isis.extensions.sse.applib.service.SseService;
 +import org.apache.isis.extensions.sse.applib.value.ListeningMarkup;
 +
 +import static org.apache.isis.core.commons.internal.base._With.requires;
 +
 +import lombok.val;
 +
 +/**
 + * Server-sent events.
 + *  
 + * @see <a href="https://www.w3schools.com/html/html5_serversentevents.asp">Server Sent Events (@ w3schools)</a>
 + * 
 + * @since 2.0
 + *
 + */
 +//@WebServlet(value="/sse", asyncSupported=true)
 +public class ServerSentEventsServlet extends HttpServlet {
 +    private static final long serialVersionUID = 1L;
 +    private ExecutorService threadPool;
 +
-     @Inject
-     private SseService sseService;
++    @Autowired private SseService sseService;
 +
 +    @Override
 +    public void init() throws ServletException {
 +        super.init();
 +        threadPool = ForkJoinPool.commonPool();
 +        
 +        requires(sseService, "sseService");
 +    }
 +
 +    @Override
 +    public void destroy() {
 +        threadPool.shutdown();
 +        super.destroy();
 +    }
 +
 +    @Override
 +    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 +
 +        val eventStreamType = parseEventStreamType(request);
 +        val eventStream = eventStreamType.flatMap(sseService::lookupByType)
 +                .orElse(null);
 +
 +        if(eventStream==null) {
 +            response.setStatus(200);
 +            response.setContentType(null);
 +            response.flushBuffer();
 +            return;
 +        }
 +
 +        response.setStatus(200);
 +        response.setContentType("text/event-stream");
 +        response.setCharacterEncoding("UTF-8");
 +        //response.setHeader("Access-Control-Allow-Origin", "*"); // not secure, can be used for debugging
 +        response.setHeader("Cache-Control", "no-cache,no-store");
 +        response.flushBuffer();
 +
 +        final AsyncContext asyncContext = request.startAsync();
 +
 +        //XXX javac explicitly requires curly-braces here (to tell it we want a Runnable)
 +        threadPool.submit(()->{fork(asyncContext, eventStream);});
 +
 +    }
 +
 +    // -- HELPER
 +
 +    private void fork(final AsyncContext asyncContext, final SseChannel eventStream) {
 +
 +        val response = asyncContext.getResponse();
 +        val marshaller = new JaxbAdapters.MarkupAdapter();
 +
 +        eventStream.listenWhile(source->{
 +
 +            if(threadPool.isShutdown()) {
 +                return false; // stop listening
 +            }
 +
 +            try {
 +
 +                val writer = response.getWriter(); // don't close the writer, its likely to be reused
 +                if(writer==null) {
 +                    return false; // stop listening
 +                }
 +
 +                val payload = marshaller.marshal(ListeningMarkup.valueOfHtml(source.getPayload()));
 +
 +                writer
 +                .append("data: ")
 +                .append(payload)
 +                .append("\n\n")
 +                .flush();
 +
 +                return true; // continue listening                
 +
 +            } catch (Exception e) {
 +                e.printStackTrace();
 +                return false; // stop listening
 +            }
 +
 +        });
 +
 +        // now we wait until the eventStream closes
 +        try {
 +            eventStream.awaitClose();
 +        } catch (InterruptedException e) {
 +            e.printStackTrace();
 +        }
 +
 +        // Completes the asynchronous operation that was started on the request
 +        // that was used to initialize this AsyncContext. 
 +        asyncContext.complete();
 +
 +    }
 +
 +    private Optional<Class<?>> parseEventStreamType(HttpServletRequest request) {
 +        val eventStreamId = request.getParameter("eventStream");
 +        if(_Strings.isNullOrEmpty(eventStreamId)) {
 +            return Optional.empty();
 +        }
 +        try {
 +            return Optional.of(_Context.loadClass(eventStreamId));
 +        } catch (Throwable e) {
 +            e.printStackTrace();
 +            return Optional.empty();
 +        }
 +    }
 +
 +}
diff --cc viewers/restfulobjects/jaxrs-resteasy-4/src/main/java/org/apache/isis/viewer/restfulobjects/jaxrsresteasy4/webmodule/WebModuleJaxrsResteasy4.java
index 63913b1,0000000..04ee46e
mode 100644,000000..100644
--- a/viewers/restfulobjects/jaxrs-resteasy-4/src/main/java/org/apache/isis/viewer/restfulobjects/jaxrsresteasy4/webmodule/WebModuleJaxrsResteasy4.java
+++ b/viewers/restfulobjects/jaxrs-resteasy-4/src/main/java/org/apache/isis/viewer/restfulobjects/jaxrsresteasy4/webmodule/WebModuleJaxrsResteasy4.java
@@@ -1,128 -1,0 +1,142 @@@
 +/*
 + *  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.isis.viewer.restfulobjects.jaxrsresteasy4.webmodule;
 +
 +import javax.inject.Inject;
 +import javax.inject.Named;
 +import javax.servlet.ServletContext;
 +import javax.servlet.ServletContextListener;
 +import javax.servlet.ServletException;
 +
++import org.jboss.resteasy.core.providerfactory.ResteasyProviderFactoryImpl;
++import org.jboss.resteasy.spi.ResteasyProviderFactory;
 +import org.springframework.beans.factory.annotation.Qualifier;
 +import org.springframework.core.annotation.Order;
 +import org.springframework.stereotype.Service;
 +
 +import org.apache.isis.applib.annotation.OrderPrecedence;
 +import org.apache.isis.applib.services.inject.ServiceInjector;
 +import org.apache.isis.core.commons.collections.Can;
 +import org.apache.isis.core.config.RestEasyConfiguration;
 +import org.apache.isis.viewer.restfulobjects.viewer.webmodule.IsisRestfulObjectsSessionFilter;
 +import org.apache.isis.viewer.restfulobjects.viewer.webmodule.IsisTransactionFilterForRestfulObjects;
 +import org.apache.isis.viewer.restfulobjects.viewer.webmodule.auth.AuthenticationSessionStrategyBasicAuth;
 +import org.apache.isis.webapp.modules.WebModuleAbstract;
 +import org.apache.isis.webapp.modules.WebModuleContext;
 +
 +import lombok.Getter;
 +
 +/**
 + * WebModule that provides the RestfulObjects Viewer.
 + * 
 + * @since 2.0
++ *
++ * @implNote CDI feels responsible to resolve injection points for any Servlet or Filter
++ * we register programmatically on the ServletContext.
++ * As long as injection points are considered to be resolved by Spring, we can workaround this fact:
++ * By replacing annotations {@code @Inject} with {@code @Autowire} for any Servlet or Filter,
++ * that get contributed by a WebModule, these will be ignored by CDI.
++ *
 + */
 +@Service
 +@Named("isisRoViewer.WebModuleJaxrsRestEasy4")
 +@Order(OrderPrecedence.MIDPOINT - 80)
 +@Qualifier("JaxrsRestEasy4")
 +public final class WebModuleJaxrsResteasy4 extends WebModuleAbstract {
 +
 +    private static final String ISIS_SESSION_FILTER_FOR_RESTFUL_OBJECTS = "IsisSessionFilterForRestfulObjects";
 +    private static final String ISIS_TRANSACTION_FILTER = "IsisTransactionFilterForRestfulObjects";
 +
 +    private final RestEasyConfiguration restEasyConfiguration;
 +
 +    private final String restfulPath;
 +    private final String urlPattern;
 +
 +    @Inject
 +    public WebModuleJaxrsResteasy4(
 +            final RestEasyConfiguration restEasyConfiguration,
 +            final ServiceInjector serviceInjector) {
 +        super(serviceInjector);
 +        this.restEasyConfiguration = restEasyConfiguration;
 +        this.restfulPath = this.restEasyConfiguration.getJaxrs().getDefaultPath() + "/";
 +        this.urlPattern = this.restfulPath + "*";
 +    }
 +
 +    @Getter
 +    private final String name = "JaxrsRestEasy4";
 +
 +    @Override
 +    public void prepare(WebModuleContext ctx) {
++
++        // forces RuntimeDelegate.getInstance() to be provided by RestEasy
++        // (and not by eg. the JEE container if any)
++        ResteasyProviderFactory.setInstance(new ResteasyProviderFactoryImpl());
++
 +        super.prepare(ctx);
 +
 +        if(!isApplicable(ctx)) {
 +            return;
 +        }
 +
 +        ctx.addViewer("restfulobjects");
 +        ctx.addProtectedPath(urlPattern);
 +    }
 +
 +    @Override
 +    public Can<ServletContextListener> init(ServletContext ctx) throws ServletException {
 +
 +        registerFilter(ctx, ISIS_SESSION_FILTER_FOR_RESTFUL_OBJECTS, IsisRestfulObjectsSessionFilter.class)
 +                .ifPresent(filterReg -> {
 +                    // this is mapped to the entire application;
 +                    // however the IsisSessionFilter will
 +                    // "notice" if the session filter has already been
 +                    // executed for the request pipeline, and if so will do nothing
 +                    filterReg.addMappingForUrlPatterns(
 +                            null,
 +                            true,
 +                            this.urlPattern);
 +
 +                    filterReg.setInitParameter(
 +                            "authenticationSessionStrategy",
 +                            AuthenticationSessionStrategyBasicAuth.class.getName());
 +                    filterReg.setInitParameter(
 +                            "whenNoSession", // what to do if no session was found ...
 +                            "auto"); // ... 401 and a basic authentication challenge if request originates from web browser
 +                    filterReg.setInitParameter(
 +                            "passThru",
 +                            String.join(",",
 +                                    this.restfulPath + "swagger",
 +                                    this.restfulPath + "health"));
 +
 +                } );
 +
 +        registerFilter(ctx, ISIS_TRANSACTION_FILTER, IsisTransactionFilterForRestfulObjects.class)
 +            .ifPresent(filterReg -> {
 +                filterReg.addMappingForUrlPatterns(
 +                        null,
 +                        true,
 +                        this.urlPattern);
 +            });
 +
 +
 +        return Can.empty(); // registers no listeners
 +    }
 +
 +
 +}
diff --cc viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/webmodule/IsisRestfulObjectsSessionFilter.java
index 5fdf4bf,0000000..d0575e4
mode 100644,000000..100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/webmodule/IsisRestfulObjectsSessionFilter.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/webmodule/IsisRestfulObjectsSessionFilter.java
@@@ -1,435 -1,0 +1,435 @@@
 +/*
 + *  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.isis.viewer.restfulobjects.viewer.webmodule;
 +
 +import java.io.IOException;
 +import java.util.Arrays;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.List;
 +import java.util.function.Function;
 +import java.util.regex.Pattern;
 +import java.util.stream.Collectors;
 +import java.util.stream.Stream;
 +
 +import javax.inject.Inject;
 +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.HttpServletRequest;
 +import javax.servlet.http.HttpServletResponse;
 +
++import org.springframework.beans.factory.annotation.Autowired;
++
 +import org.apache.isis.core.commons.internal.base._Strings;
 +import org.apache.isis.core.commons.internal.collections._Lists;
 +import org.apache.isis.core.commons.internal.factory.InstanceUtil;
 +import org.apache.isis.core.metamodel.commons.StringExtensions;
 +import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 +import org.apache.isis.core.metamodel.specloader.validator.MetaModelInvalidException;
 +import org.apache.isis.runtime.session.IsisSessionFactory;
 +import org.apache.isis.viewer.restfulobjects.viewer.webmodule.auth.AuthenticationSessionStrategy;
 +import org.apache.isis.viewer.restfulobjects.viewer.webmodule.auth.AuthenticationSessionStrategyDefault;
 +import org.apache.isis.webapp.modules.templresources.TemplateResourceCachingFilter;
 +
 +import static org.apache.isis.core.commons.internal.base._With.requires;
 +
 +import lombok.val;
 +
 +/**
 + * Filter for RestfulObjects.
 + * 
 + * authenticate user, set up an Isis session
 + */
 +//@WebFilter(
 +//        servletNames={"RestfulObjectsRestEasyDispatcher"}, // this is mapped to the entire application; 
 +//            // however the IsisSessionFilter will 
 +//            // "notice" if the session filter has already been
 +//            // executed for the request pipeline, and if so will do nothing
 +//        initParams={
 +//        @WebInitParam(
 +//                name="authenticationSessionStrategy", 
 +//                value="org.apache.isis.viewer.restfulobjects.server.authentication.AuthenticationSessionStrategyBasicAuth"), // authentication required for REST
 +//        @WebInitParam(
 +//                name="whenNoSession", // what to do if no session was found ...
 +//                value="auto"), // ... 401 and a basic authentication challenge if request originates from web browser
 +//        @WebInitParam(name="passThru", value="/restful/swagger,/restful/health") //TODO[ISIS-1895] the restful path is configured elsewhere
 +//})
 +public class IsisRestfulObjectsSessionFilter implements Filter {
 +
 +    /**
 +     * Recommended standard init parameter key for filters and servlets to
 +     * lookup an implementation of {@link AuthenticationSessionStrategy}.
 +     */
 +    public static final String AUTHENTICATION_SESSION_STRATEGY_KEY = "authenticationSessionStrategy";
 +
 +    /**
 +     * Default value for {@link #AUTHENTICATION_SESSION_STRATEGY_KEY} if not specified.
 +     */
 +    public static final String AUTHENTICATION_SESSION_STRATEGY_DEFAULT = AuthenticationSessionStrategyDefault.class.getName();
 +
 +    /**
 +     * Init parameter key for backward compatibility; if logonPage set then
 +     * assume 'restricted' handling.
 +     */
 +    public static final String LOGON_PAGE_KEY = "logonPage";
 +
 +
 +    /**
 +     * Init parameter key for what should be done if no session was found.
 +     *
 +     * <p>
 +     * Valid values are:
 +     * <ul>
 +     * <li>unauthorized - issue a 401 response.
 +     * <li>basicAuthChallenge - issue a basic auth 401 challenge. The idea here
 +     * is that the configured logon strategy should handle the next request
 +     * <li>restricted - allow access but only to a restricted (comma-separated)
 +     * list of paths. Access elsewhere should be redirected to the first of
 +     * these paths
 +     * <li>continue - allow the request to continue (eg if there is no security
 +     * requirements)
 +     * </ul>
 +     */
 +    public static final String WHEN_NO_SESSION_KEY = "whenNoSession";
 +
 +    /**
 +     * Which URLs to ignore (eg <code>/restful/swagger</code> so that swagger specs can be accessed from the swagger-ui)
 +     */
 +    public static final String PASS_THRU_KEY = "passThru";
 +
 +    /**
 +     * Init parameter key to read the restricted list of paths (if
 +     * {@link #WHEN_NO_SESSION_KEY} is for {@link WhenNoSession#RESTRICTED}).
 +     *
 +     * <p>
 +     * The servlets mapped to these paths are expected to be able to deal with
 +     * there being no session. Typically they will be logon pages.
 +     */
 +    public static final String RESTRICTED_KEY = "restricted";
 +
 +    /**
 +     * Init parameter key to redirect to if an exception occurs.
 +     */
 +    public static final String REDIRECT_TO_ON_EXCEPTION_KEY = "redirectToOnException";
 +
 +    /**
 +     * Init parameter key for which extensions should be ignored (typically,
 +     * mappings for other viewers within the webapp context).
 +     *
 +     * <p>
 +     * It can also be used to specify ignored static resources (though putting
 +     * the {@link TemplateResourceCachingFilter} first in the <tt>web.xml</tt>
 +     * accomplishes the same thing).
 +     *
 +     * <p>
 +     * The value is expected as a comma separated list.
 +     */
 +    public static final String IGNORE_EXTENSIONS_KEY = "ignoreExtensions";
 +    /**
 +     * Somewhat hacky, add this to the query
 +     */
 +    public static final String ISIS_SESSION_FILTER_QUERY_STRING_FORCE_LOGOUT = "__isis_force_logout";
 +
 +    private static final Function<String, Pattern> STRING_TO_PATTERN = (final String input) -> {
 +        return Pattern.compile(".*\\." + input);
 +    };
 +
-     @Inject
-     private IsisSessionFactory isisSessionFactory;
-     @Inject
-     private SpecificationLoader specificationLoader;
++    @Autowired private IsisSessionFactory isisSessionFactory;
++    @Autowired private SpecificationLoader specificationLoader;
 +    
 +    private List<String> passThruList = Collections.emptyList();
 +
 +    static void redirect(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, final String redirectTo) throws IOException {
 +        httpResponse.sendRedirect(StringExtensions.combinePath(httpRequest.getContextPath(), redirectTo));
 +    }
 +
 +    public enum WhenNoSession {
 +        UNAUTHORIZED("unauthorized") {
 +            @Override
 +            public void handle(final IsisRestfulObjectsSessionFilter filter, final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, final FilterChain chain) throws IOException, ServletException {
 +                httpResponse.sendError(401);
 +            }
 +        },
 +        BASIC_AUTH_CHALLENGE("basicAuthChallenge") {
 +            @Override
 +            public void handle(final IsisRestfulObjectsSessionFilter filter, final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, final FilterChain chain) throws IOException, ServletException {
 +                httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"Apache Isis\"");
 +                httpResponse.sendError(401);
 +            }
 +        },
 +        AUTO("auto") {
 +            @Override
 +            public void handle(final IsisRestfulObjectsSessionFilter filter, final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, final FilterChain chain) throws IOException, ServletException {
 +                if(fromWebBrowser(httpRequest)) {
 +                    httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"Apache Isis\"");
 +                }
 +                httpResponse.sendError(401);
 +            }
 +
 +            private boolean fromWebBrowser(final HttpServletRequest httpRequest) {
 +                String accept = httpRequest.getHeader("Accept");
 +                return accept.contains("text/html");
 +            }
 +        },
 +        /**
 +         * the destination servlet is expected to know that there will be no open session, and handle the case appropriately
 +         */
 +        CONTINUE("continue") {
 +            @Override
 +            public void handle(final IsisRestfulObjectsSessionFilter filter, final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, final FilterChain chain) throws IOException, ServletException {
 +                chain.doFilter(httpRequest, httpResponse);
 +            }
 +        },
 +        /**
 +         * Allow access to a restricted list of URLs (else redirect to the first of that list of URLs)
 +         */
 +        RESTRICTED("restricted") {
 +            @Override
 +            public void handle(final IsisRestfulObjectsSessionFilter filter, final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, final FilterChain chain) throws IOException, ServletException {
 +
 +                if (filter.restrictedPaths.contains(httpRequest.getServletPath())) {
 +                    chain.doFilter(httpRequest, httpResponse);
 +                    return;
 +                }
 +                redirect(httpRequest, httpResponse, filter.restrictedPaths.get(0));
 +            }
 +
 +        };
 +        private final String initParamValue;
 +
 +        private WhenNoSession(final String initParamValue) {
 +            this.initParamValue = initParamValue;
 +        }
 +
 +        public static WhenNoSession lookup(final String whenNoSessionStr) {
 +            for (final WhenNoSession wns : values()) {
 +                if (wns.initParamValue.equals(whenNoSessionStr)) {
 +                    return wns;
 +                }
 +            }
 +            throw new IllegalStateException("require an init-param of '" + WHEN_NO_SESSION_KEY + "', taking a value of " + WhenNoSession.values());
 +        }
 +
 +        public abstract void handle(IsisRestfulObjectsSessionFilter filter, HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain) throws IOException, ServletException;
 +    }
 +
 +
 +    private AuthenticationSessionStrategy authSessionStrategy;
 +    private List<String> restrictedPaths;
 +    private WhenNoSession whenNotAuthenticated;
 +    private String redirectToOnException;
 +    private Collection<Pattern> ignoreExtensions;
 +
 +
 +    
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // init, destroy
 +    // /////////////////////////////////////////////////////////////////
 +
 +    @Override
 +    public void init(final FilterConfig config) throws ServletException {
 +        authSessionStrategy = lookup(config.getInitParameter(AUTHENTICATION_SESSION_STRATEGY_KEY));
 +        lookupWhenNoSession(config);
 +        lookupPassThru(config);
 +        lookupRedirectToOnException(config);
 +        lookupIgnoreExtensions(config);
 +    }
 +
 +    /**
 +     * Public visibility so can also be used by servlets.
 +     */
 +    public static AuthenticationSessionStrategy lookup(String authLookupStrategyClassName) {
 +        if (authLookupStrategyClassName == null) {
 +            authLookupStrategyClassName = AUTHENTICATION_SESSION_STRATEGY_DEFAULT;
 +        }
 +        return (AuthenticationSessionStrategy) InstanceUtil.createInstance(authLookupStrategyClassName);
 +    }
 +
 +    private void lookupWhenNoSession(final FilterConfig config) {
 +
 +        final String whenNoSessionStr = config.getInitParameter(WHEN_NO_SESSION_KEY);
 +
 +        // backward compatibility
 +        final String logonPage = config.getInitParameter(LOGON_PAGE_KEY);
 +        if (logonPage != null) {
 +            if (whenNoSessionStr != null) {
 +                throw new IllegalStateException(String.format(
 +                        "The init-param '%s' is only provided for backwards compatibility; "
 +                                + "remove if the init-param '%s' has been specified", LOGON_PAGE_KEY, WHEN_NO_SESSION_KEY));
 +            } else {
 +                // default whenNotAuthenticated and allow access through to the logonPage
 +                whenNotAuthenticated = WhenNoSession.RESTRICTED;
 +                this.restrictedPaths = _Lists.of(logonPage);
 +                return;
 +            }
 +        }
 +
 +        whenNotAuthenticated = WhenNoSession.lookup(whenNoSessionStr);
 +        if (whenNotAuthenticated == WhenNoSession.RESTRICTED) {
 +            final String restrictedPathsStr = config.getInitParameter(RESTRICTED_KEY);
 +            if (restrictedPathsStr == null) {
 +                throw new IllegalStateException(String.format("Require an init-param of '%s' key to be set.", RESTRICTED_KEY));
 +            }
 +            this.restrictedPaths = 
 +                    _Strings.splitThenStream(restrictedPathsStr, ",")
 +                    .collect(Collectors.toList());
 +
 +        }
 +
 +    }
 +
 +    void lookupPassThru(final FilterConfig config) {
 +        this.passThruList = lookupAndParsePassThru(config);
 +    }
 +
 +    List<String> lookupAndParsePassThru(final FilterConfig config) {
 +        final String passThru = config.getInitParameter(PASS_THRU_KEY);
 +        return passThru != null && !passThru.equals("")
 +                ? Arrays.asList(passThru.split(","))
 +                        : Collections.<String>emptyList();
 +    }
 +
 +    private void lookupRedirectToOnException(final FilterConfig config) {
 +        redirectToOnException = config.getInitParameter(REDIRECT_TO_ON_EXCEPTION_KEY);
 +    }
 +
 +    private void lookupIgnoreExtensions(final FilterConfig config) {
 +        ignoreExtensions = Collections.unmodifiableCollection(parseIgnorePatterns(config)
 +                .collect(Collectors.toList()));
 +    }
 +
 +    private Stream<Pattern> parseIgnorePatterns(final FilterConfig config) {
 +        final String ignoreExtensionsStr = config.getInitParameter(IGNORE_EXTENSIONS_KEY);
 +        if (ignoreExtensionsStr != null) {
 +            final Stream<String> ignoreExtensions = _Strings.splitThenStream(ignoreExtensionsStr, ","); 
 +            return ignoreExtensions.map(STRING_TO_PATTERN);
 +        }
 +        return Stream.empty();
 +    }
 +
 +
 +    @Override
 +    public void destroy() {
 +    }
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // doFilter
 +    // /////////////////////////////////////////////////////////////////
 +
 +    @Override
 +    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
 +
 +        requires(isisSessionFactory, "isisSessionFactory");
 +        requires(specificationLoader, "specificationLoader");
 +        
 +        ensureMetamodelIsValid(specificationLoader);
 +
 +        val httpServletRequest = (HttpServletRequest) request;
 +        val httpServletResponse = (HttpServletResponse) response;
 +
 +        try {
 +            val queryString = httpServletRequest.getQueryString();
 +            if (queryString != null && queryString
 +                    .contains(ISIS_SESSION_FILTER_QUERY_STRING_FORCE_LOGOUT)) {
 +
 +                authSessionStrategy.invalidate(httpServletRequest, httpServletResponse);
 +                return;
 +            }
 +
 +            if (requestIsIgnoreExtension(this, httpServletRequest) ||
 +                    TemplateResourceCachingFilter.isCachedResource(httpServletRequest)) {
 +                chain.doFilter(request, response);
 +                return;
 +            }
 +
 +            if(requestIsPassThru(httpServletRequest)) {
 +                chain.doFilter(request, response);
 +                return;
 +            }
 +            
 +            // authenticate
 +            val authenticationSession =
 +                    authSessionStrategy.lookupValid(httpServletRequest, httpServletResponse);
 +            if (authenticationSession != null) {
 +               
 +                authSessionStrategy.bind(httpServletRequest, httpServletResponse, authenticationSession);
 +
 +                isisSessionFactory.openSession(authenticationSession);
 +                chain.doFilter(request, response);
 +                
 +                return;
 +            }
 +
 +            try {
 +                whenNotAuthenticated.handle(this, httpServletRequest, httpServletResponse, chain);
 +            } catch (final RuntimeException | IOException | ServletException ex) {
 +                // in case the destination servlet cannot cope, but we've
 +                // been told to redirect elsewhere
 +                if (redirectToOnException != null) {
 +                    redirect(httpServletRequest, httpServletResponse, redirectToOnException);
 +                    return;
 +                }
 +                throw ex;
 +            }
 +
 +        } finally {
 +            isisSessionFactory.closeSession();
 +        }
 +
 +    }
 +
 +
 +    private static void ensureMetamodelIsValid(SpecificationLoader specificationLoader) {
 +        val validationResult = specificationLoader.getValidationResult();
 +        if(validationResult.hasFailures()) {
 +            throw new MetaModelInvalidException(validationResult.getAsLineNumberedString());
 +        }
 +    }
 +
 +
 +    protected boolean requestIsPassThru(final HttpServletRequest httpServletRequest) {
 +        final String requestURI = httpServletRequest.getRequestURI();
 +        for (final String passThru : passThruList) {
 +            if(requestURI.startsWith(passThru)) {
 +                return true;
 +            }
 +        }
 +        return false;
 +    }
 +
 +    private boolean requestIsIgnoreExtension(
 +            final IsisRestfulObjectsSessionFilter filter, 
 +            final HttpServletRequest httpRequest) {
 +        
 +        val servletPath = httpRequest.getServletPath();
 +        for (final Pattern extension : filter.ignoreExtensions) {
 +            if (extension.matcher(servletPath).matches()) {
 +                return true;
 +            }
 +        }
 +        return false;
 +    }
 +
 +}