keycloak-aplcache

Merge pull request #3563 from mposolda/undertow-adapter-tests KEYCLOAK-3124

11/29/2016 7:41:48 PM

Changes

.travis.yml 1(+1 -0)

Details

.travis.yml 1(+1 -0)

diff --git a/.travis.yml b/.travis.yml
index ef531a2..faf7b47 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,6 +16,7 @@ env:
     - TESTS=group1
     - TESTS=group2
     - TESTS=group3
+    - TESTS=group4
     - TESTS=adapter
 
 jdk:
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
index 0dbd7a9..ff476ed 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
@@ -66,6 +66,14 @@
             <artifactId>keycloak-dependencies-server-all</artifactId>
             <type>pom</type>
         </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-undertow-adapter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-servlet-filter-adapter</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java
index f740217..9d29973 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java
@@ -18,10 +18,13 @@
 package org.keycloak.testsuite.arquillian.undertow;
 
 import io.undertow.Undertow;
+import io.undertow.server.handlers.PathHandler;
 import io.undertow.servlet.Servlets;
 import io.undertow.servlet.api.DefaultServletConfig;
 import io.undertow.servlet.api.DeploymentInfo;
+import io.undertow.servlet.api.DeploymentManager;
 import io.undertow.servlet.api.FilterInfo;
+import io.undertow.servlet.api.ServletContainer;
 import io.undertow.servlet.api.ServletInfo;
 import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
 import org.jboss.arquillian.container.spi.client.container.DeploymentException;
@@ -34,14 +37,20 @@ import org.jboss.logging.Logger;
 import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
 import org.jboss.resteasy.spi.ResteasyDeployment;
 import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
 import org.jboss.shrinkwrap.descriptor.api.Descriptor;
 import org.jboss.shrinkwrap.undertow.api.UndertowWebArchive;
+import org.keycloak.common.util.reflections.Reflections;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.services.filters.KeycloakSessionServletFilter;
 import org.keycloak.services.resources.KeycloakApplication;
 
 import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+
+import java.lang.reflect.Field;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Map;
 
 public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUndertowConfiguration> {
@@ -52,6 +61,8 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
     private KeycloakOnUndertowConfiguration configuration;
     private KeycloakSessionFactory sessionFactory;
 
+    Map<String, String> deployedArchivesToContextPath = new HashMap<>();
+
     private DeploymentInfo createAuthServerDeploymentInfo() {
         ResteasyDeployment deployment = new ResteasyDeployment();
         deployment.setApplicationClass(KeycloakApplication.class.getName());
@@ -75,6 +86,8 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
     public DeploymentInfo getDeplotymentInfoFromArchive(Archive<?> archive) {
         if (archive instanceof UndertowWebArchive) {
             return ((UndertowWebArchive) archive).getDeploymentInfo();
+        } else if (archive instanceof WebArchive) {
+            return new UndertowDeployerHelper().getDeploymentInfo(configuration, (WebArchive)archive);
         } else {
             throw new IllegalArgumentException("UndertowContainer only supports UndertowWebArchive or WebArchive.");
         }
@@ -93,7 +106,19 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
     @Override
     public ProtocolMetaData deploy(Archive<?> archive) throws DeploymentException {
         DeploymentInfo di = getDeplotymentInfoFromArchive(archive);
-        undertow.deploy(di);
+
+        ClassLoader parentCl = Thread.currentThread().getContextClassLoader();
+        UndertowWarClassLoader classLoader = new UndertowWarClassLoader(parentCl, archive);
+        Thread.currentThread().setContextClassLoader(classLoader);
+
+        try {
+            undertow.deploy(di);
+        } finally {
+            Thread.currentThread().setContextClassLoader(parentCl);
+        }
+
+        deployedArchivesToContextPath.put(archive.getName(), di.getContextPath());
+
         return new ProtocolMetaData().addContext(
                 createHttpContextForDeploymentInfo(di));
     }
@@ -151,7 +176,29 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
 
     @Override
     public void undeploy(Archive<?> archive) throws DeploymentException {
-        throw new UnsupportedOperationException("Not implemented");
+        Field containerField = Reflections.findDeclaredField(UndertowJaxrsServer.class, "container");
+        Reflections.setAccessible(containerField);
+        ServletContainer container = (ServletContainer) Reflections.getFieldValue(containerField, undertow);
+
+        DeploymentManager deployment = container.getDeployment(archive.getName());
+        if (deployment != null) {
+            try {
+                deployment.stop();
+            } catch (ServletException se) {
+                throw new DeploymentException(se.getMessage(), se);
+            }
+
+            deployment.undeploy();
+
+            Field rootField = Reflections.findDeclaredField(UndertowJaxrsServer.class, "root");
+            Reflections.setAccessible(rootField);
+            PathHandler root = (PathHandler) Reflections.getFieldValue(rootField, undertow);
+
+            String path = deployedArchivesToContextPath.get(archive.getName());
+            root.removePrefixPath(path);
+        } else {
+            log.warnf("Deployment '%s' not found", archive.getName());
+        }
     }
 
     @Override
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SimpleWebXmlParser.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SimpleWebXmlParser.java
new file mode 100644
index 0000000..18bceb2
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SimpleWebXmlParser.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.arquillian.undertow;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+
+import io.undertow.servlet.api.DeploymentInfo;
+import io.undertow.servlet.api.FilterInfo;
+import io.undertow.servlet.api.LoginConfig;
+import io.undertow.servlet.api.SecurityConstraint;
+import io.undertow.servlet.api.SecurityInfo;
+import io.undertow.servlet.api.ServletInfo;
+import io.undertow.servlet.api.WebResourceCollection;
+import org.jboss.logging.Logger;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Simple web.xml parser just to handle our test deployments
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+class SimpleWebXmlParser {
+
+    private static final Logger log = Logger.getLogger(SimpleWebXmlParser.class);
+
+    void parseWebXml(Document webXml, DeploymentInfo di) {
+        try {
+            DocumentWrapper document = new DocumentWrapper(webXml);
+
+            // SERVLETS
+
+            Map<String, String> servletMappings = new HashMap<>();
+            List<ElementWrapper> sm = document.getElementsByTagName("servlet-mapping");
+            for (ElementWrapper mapping : sm) {
+                String servletName = mapping.getElementByTagName("servlet-name").getText();
+                String path = mapping.getElementByTagName("url-pattern").getText();
+                servletMappings.put(servletName, path);
+            }
+
+            List<ElementWrapper> servlets = document.getElementsByTagName("servlet");
+            for (ElementWrapper servlet : servlets) {
+                String servletName = servlet.getElementByTagName("servlet-name").getText();
+                String servletClass = servlet.getElementByTagName("servlet-class").getText();
+
+                Class<? extends Servlet> servletClazz = (Class<? extends Servlet>) Class.forName(servletClass);
+                ServletInfo undertowServlet = new ServletInfo(servletName, servletClazz);
+
+                if (servletMappings.containsKey(servletName)) {
+                    undertowServlet.addMapping(servletMappings.get(servletName));
+                    di.addServlet(undertowServlet);
+                } else {
+                    log.warnf("Missing servlet-mapping for '%s'", servletName);
+                }
+            }
+
+            // FILTERS
+            Map<String, String> filterMappings = new HashMap<>();
+            List<ElementWrapper> fm = document.getElementsByTagName("filter-mapping");
+            for (ElementWrapper mapping : fm) {
+                String filterName = mapping.getElementByTagName("filter-name").getText();
+                String path = mapping.getElementByTagName("url-pattern").getText();
+                filterMappings.put(filterName, path);
+            }
+
+            List<ElementWrapper> filters = document.getElementsByTagName("filter");
+            for (ElementWrapper filter : filters) {
+                String filterName = filter.getElementByTagName("filter-name").getText();
+                String filterClass = filter.getElementByTagName("filter-class").getText();
+
+                Class<? extends Filter> filterClazz = (Class<? extends Filter>) Class.forName(filterClass);
+                FilterInfo undertowFilter = new FilterInfo(filterName, filterClazz);
+                di.addFilter(undertowFilter);
+
+                if (filterMappings.containsKey(filterName)) {
+                    di.addFilterUrlMapping(filterName, filterMappings.get(filterName), DispatcherType.REQUEST);
+                } else {
+                    log.warnf("Missing filter-mapping for '%s'", filterName);
+                }
+            }
+
+            // CONTEXT PARAMS
+            List<ElementWrapper> contextParams = document.getElementsByTagName("context-param");
+            for (ElementWrapper param : contextParams) {
+                String paramName = param.getElementByTagName("param-name").getText();
+                String paramValue = param.getElementByTagName("param-value").getText();
+                di.addInitParameter(paramName, paramValue);
+            }
+
+
+            // ROLES
+            List<ElementWrapper> securityRoles = document.getElementsByTagName("security-role");
+            for (ElementWrapper sr : securityRoles) {
+                String roleName = sr.getElementByTagName("role-name").getText();
+                di.addSecurityRole(roleName);
+            }
+
+
+            // SECURITY CONSTRAINTS
+            List<ElementWrapper> secConstraints = document.getElementsByTagName("security-constraint");
+            for (ElementWrapper constraint : secConstraints) {
+                String urlPattern = constraint.getElementByTagName("web-resource-collection")
+                        .getElementByTagName("url-pattern")
+                        .getText();
+
+                ElementWrapper authCsnt = constraint.getElementByTagName("auth-constraint");
+                String roleName = authCsnt==null ? null : authCsnt
+                        .getElementByTagName("role-name")
+                        .getText();
+
+                SecurityConstraint undertowConstraint = new SecurityConstraint();
+                WebResourceCollection collection = new WebResourceCollection();
+                collection.addUrlPattern(urlPattern);
+                undertowConstraint.addWebResourceCollection(collection);
+
+                if (roleName != null) {
+                    undertowConstraint.addRoleAllowed(roleName);
+                } else {
+                    undertowConstraint.setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT);
+                }
+                di.addSecurityConstraint(undertowConstraint);
+            }
+
+            // LOGIN CONFIG
+            ElementWrapper loginCfg = document.getElementByTagName("login-config");
+            if (loginCfg != null) {
+                String mech = loginCfg.getElementByTagName("auth-method").getText();
+                String realmName = loginCfg.getElementByTagName("realm-name").getText();
+
+                ElementWrapper form = loginCfg.getElementByTagName("form-login-config");
+                if (form != null) {
+                    String loginPage = form.getElementByTagName("form-login-page").getText();
+                    String errorPage = form.getElementByTagName("form-error-page").getText();
+                    di.setLoginConfig(new LoginConfig(mech, realmName, loginPage, errorPage));
+                } else {
+                    di.setLoginConfig(new LoginConfig(realmName).addFirstAuthMethod(mech));
+                }
+            }
+
+        } catch (ClassNotFoundException cnfe) {
+            throw new RuntimeException(cnfe);
+        }
+    }
+
+
+    private static abstract class XmlWrapper {
+
+
+        abstract List<ElementWrapper> getElementsByTagName(String tagName);
+
+
+        abstract ElementWrapper getElementByTagName(String tagName);
+
+
+        List<ElementWrapper> getElementsFromNodeList(NodeList nl) {
+            List<ElementWrapper> result = new LinkedList<>();
+
+            for (int i=0; i<nl.getLength() ; i++) {
+                Node node = nl.item(i);
+                if (node instanceof Element) {
+                    result.add(new ElementWrapper((Element) node));
+                }
+            }
+
+            return result;
+        }
+
+
+        ElementWrapper getElementFromNodeList(NodeList nl) {
+            if (nl.getLength() > 0) {
+                return new ElementWrapper((Element) nl.item(0));
+            } else {
+                return null;
+            }
+        }
+
+    }
+
+
+    private static class ElementWrapper extends XmlWrapper {
+
+        private final Element element;
+
+        public ElementWrapper(Element element) {
+            this.element = element;
+        }
+
+        @Override
+        public List<ElementWrapper> getElementsByTagName(String tagName) {
+            NodeList nl = element.getElementsByTagName(tagName);
+            return getElementsFromNodeList(nl);
+        }
+
+        @Override
+        public ElementWrapper getElementByTagName(String tagName) {
+            NodeList nl = element.getElementsByTagName(tagName);
+            return getElementFromNodeList(nl);
+        }
+
+        public String getText() {
+            return this.element.getTextContent();
+        }
+    }
+
+
+    private static class DocumentWrapper extends XmlWrapper {
+
+        private final Document document;
+
+        public DocumentWrapper(Document document) {
+            this.document = document;
+        }
+
+
+        @Override
+        List<ElementWrapper> getElementsByTagName(String tagName) {
+            NodeList nl = document.getElementsByTagName(tagName);
+            return getElementsFromNodeList(nl);
+        }
+
+
+        @Override
+        ElementWrapper getElementByTagName(String tagName) {
+            NodeList nl = document.getElementsByTagName(tagName);
+            return getElementFromNodeList(nl);
+        }
+    }
+
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/UndertowDeployerHelper.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/UndertowDeployerHelper.java
new file mode 100644
index 0000000..bd632c3
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/UndertowDeployerHelper.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.arquillian.undertow;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.Map;
+
+import javax.servlet.Servlet;
+import javax.servlet.annotation.WebServlet;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import io.undertow.UndertowMessages;
+import io.undertow.server.handlers.resource.Resource;
+import io.undertow.server.handlers.resource.ResourceChangeListener;
+import io.undertow.server.handlers.resource.ResourceManager;
+import io.undertow.server.handlers.resource.URLResource;
+import io.undertow.servlet.api.DeploymentInfo;
+import io.undertow.servlet.api.ServletInfo;
+import org.jboss.logging.Logger;
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.ArchivePath;
+import org.jboss.shrinkwrap.api.Node;
+import org.jboss.shrinkwrap.api.asset.ClassAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+class UndertowDeployerHelper {
+
+    private static final Logger log = Logger.getLogger(UndertowDeployerHelper.class);
+
+    DeploymentInfo getDeploymentInfo(KeycloakOnUndertowConfiguration config, WebArchive archive) {
+        String archiveName = archive.getName();
+        String contextPath = "/" + archive.getName().substring(0, archive.getName().lastIndexOf('.'));
+        String appContextUrl = "http://" + config.getBindAddress() + ":" + config.getBindHttpPort() + contextPath;
+
+        try {
+            DeploymentInfo di = new DeploymentInfo();
+
+            UndertowWarClassLoader classLoader = new UndertowWarClassLoader(UndertowDeployerHelper.class.getClassLoader(), archive);
+            di.setClassLoader(classLoader);
+
+            di.setDeploymentName(archiveName);
+            di.setContextPath(contextPath);
+
+            ResourceManager undertowResourcesWrapper = getResourceManager(appContextUrl, archive);
+            di.setResourceManager(undertowResourcesWrapper);
+
+            if (archive.contains("/WEB-INF/web.xml")) {
+                Document webXml = loadXML(archive.get("/WEB-INF/web.xml").getAsset().openStream());
+                new SimpleWebXmlParser().parseWebXml(webXml, di);
+            }
+
+            addAnnotatedServlets(di, archive);
+
+            return di;
+        } catch (Exception ioe) {
+            throw new RuntimeException("Error deploying " + archive.getName(), ioe);
+        }
+    }
+
+
+    private ResourceManager getResourceManager(final String appServerRoot, final WebArchive archive) throws IOException {
+        return new ResourceManager() {
+
+            @Override
+            public Resource getResource(String path) throws IOException {
+                if (path == null || path.isEmpty()) {
+                    return null;
+                }
+
+                Node node = archive.get(path);
+                if (node == null) {
+                    log.warnf("Application '%s' did not found resource on path %s", archive.getName(), path);
+                    return null;
+                } else {
+                    URL contextUrl = new URL(appServerRoot);
+
+                    URL myResourceUrl = new URL(contextUrl.getProtocol(), contextUrl.getHost(), contextUrl.getPort(), path, new URLStreamHandler() {
+
+                        @Override
+                        protected URLConnection openConnection(URL u) throws IOException {
+                            return new URLConnection(u) {
+
+                                @Override
+                                public void connect() throws IOException {
+                                }
+
+                                @Override
+                                public InputStream getInputStream() throws IOException {
+                                    return node.getAsset().openStream();
+                                }
+
+                            };
+                        }
+
+                    });
+
+                    return new URLResource(myResourceUrl, myResourceUrl.openConnection(), path);
+                }
+            }
+
+            @Override
+            public boolean isResourceChangeListenerSupported() {
+                return false;
+            }
+
+            @Override
+            public void registerResourceChangeListener(ResourceChangeListener listener) {
+                throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
+            }
+
+            @Override
+            public void removeResourceChangeListener(ResourceChangeListener listener) {
+                throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
+            }
+
+
+            @Override
+            public void close() throws IOException {
+                // TODO: Should close open streams?
+            }
+
+        };
+    }
+
+
+    private Document loadXML(InputStream is) {
+        try {
+            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+            return dBuilder.parse(is);
+        } catch (ParserConfigurationException | SAXException | IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    private void addAnnotatedServlets(DeploymentInfo di, Archive<?> archive) {
+        Map<ArchivePath, Node> classNodes = archive.getContent((ArchivePath path) -> {
+
+            String stringPath = path.get();
+            return (stringPath.startsWith("/WEB-INF/classes") && stringPath.endsWith("class"));
+
+        });
+
+        for (Map.Entry<ArchivePath, Node> entry : classNodes.entrySet()) {
+            Node n = entry.getValue();
+            ClassAsset classAsset = (ClassAsset) n.getAsset();
+            Class<?> clazz = classAsset.getSource();
+
+            WebServlet annotation = clazz.getAnnotation(WebServlet.class);
+            if (annotation != null) {
+                ServletInfo undertowServlet = new ServletInfo(clazz.getSimpleName(), (Class<? extends Servlet>) clazz);
+
+                String[] mappings = annotation.value();
+                if (mappings != null) {
+                    for (String urlPattern : mappings) {
+                        undertowServlet.addMapping(urlPattern);
+                    }
+                }
+
+                di.addServlet(undertowServlet);
+            }
+        }
+
+    }
+
+
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/UndertowWarClassLoader.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/UndertowWarClassLoader.java
new file mode 100644
index 0000000..895ae81
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/UndertowWarClassLoader.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.arquillian.undertow;
+
+
+
+import java.io.InputStream;
+
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.Node;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UndertowWarClassLoader extends ClassLoader {
+
+    private final Archive<?> archive;
+
+    public UndertowWarClassLoader(ClassLoader parent, Archive<?> archive) {
+        super(parent);
+        this.archive = archive;
+    }
+
+
+    @Override
+    public InputStream getResourceAsStream(String name) {
+        InputStream is = super.getResourceAsStream(name);
+        if (is == null) {
+            String resourcePath = "/WEB-INF/classes";
+            if (!name.startsWith("/")) {
+                resourcePath = resourcePath + "/";
+            }
+            resourcePath = resourcePath + name;
+
+            Node node = archive.get(resourcePath);
+            if (node == null) {
+                return null;
+            } else {
+                return node.getAsset().openStream();
+            }
+        } else {
+            return is;
+        }
+    }
+
+}
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java
index b4fd9a5..c6a72e1 100644
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java
@@ -18,6 +18,7 @@
 package org.keycloak.testsuite.adapter.servlet;
 
 import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.common.util.UriUtils;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
@@ -50,13 +51,7 @@ public class CustomerServlet extends HttpServlet {
             
             //Clear principal form database-service by calling logout
             StringBuilder result = new StringBuilder();
-            String urlBase;
-
-            if (System.getProperty("app.server.ssl.required", "false").equals("true")) {
-                urlBase = System.getProperty("app.server.ssl.base.url", "https://localhost:8643");
-            } else {
-                urlBase = System.getProperty("app.server.base.url", "http://localhost:8280");
-            }
+            String urlBase = ServletTestUtils.getUrlBase(req);
 
             URL url = new URL(urlBase + "/customer-db/");
             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
@@ -78,13 +73,7 @@ public class CustomerServlet extends HttpServlet {
 
         //try {
         StringBuilder result = new StringBuilder();
-        String urlBase;
-
-        if (System.getProperty("app.server.ssl.required", "false").equals("true")) {
-            urlBase = System.getProperty("app.server.ssl.base.url", "https://localhost:8643");
-        } else {
-            urlBase = System.getProperty("app.server.base.url", "http://localhost:8280");
-        }
+        String urlBase = ServletTestUtils.getUrlBase(req);
 
         URL url = new URL(urlBase + "/customer-db/");
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
@@ -114,4 +103,5 @@ public class CustomerServlet extends HttpServlet {
 //            client.close();
 //        }
     }
+
 }
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/InputServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/InputServlet.java
index 5fb04e9..9fce3d0 100644
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/InputServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/InputServlet.java
@@ -18,6 +18,7 @@
 package org.keycloak.testsuite.adapter.servlet;
 
 import org.junit.Assert;
+import org.keycloak.common.util.UriUtils;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
@@ -37,12 +38,7 @@ public class InputServlet extends HttpServlet {
 
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        String appBase;
-        if (System.getProperty("app.server.ssl.required", "false").equals("true")) {
-            appBase = System.getProperty("app.server.ssl.base.url", "https://localhost:8643");
-        } else {
-            appBase = System.getProperty("app.server.base.url", "http://localhost:8280");
-        }
+        String appBase = ServletTestUtils.getUrlBase(req);
         String actionUrl = appBase + "/input-portal/secured/post";
 
         if (req.getRequestURI().endsWith("insecure")) {
@@ -82,7 +78,7 @@ public class InputServlet extends HttpServlet {
 
         resp.setContentType("text/plain");
         PrintWriter pw = resp.getWriter();
-        pw.printf("parameter="+req.getParameter("parameter"));
+        pw.printf("parameter=" + req.getParameter("parameter"));
         pw.flush();
     }
 
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/OfflineTokenServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/OfflineTokenServlet.java
index b40fc89..f1a0a2b 100644
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/OfflineTokenServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/OfflineTokenServlet.java
@@ -13,9 +13,6 @@ import java.io.IOException;
  */
 public class OfflineTokenServlet extends AbstractShowTokensServlet {
 
-    private static final String OFFLINE_CLIENT_APP_URI = (System.getProperty("app.server.ssl.required", "false").equals("true")) ?
-            System.getProperty("app.server.ssl.base.url", "https://localhost:8643") + "/offline-client" :
-            System.getProperty("app.server.base.url", "http://localhost:8280") + "/offline-client";
     private static final String ADAPTER_ROOT_URL = (System.getProperty("auth.server.ssl.required", "false").equals("true")) ?
             System.getProperty("auth.server.ssl.base.url", "https://localhost:8543") :
             System.getProperty("auth.server.base.url", "http://localhost:8180");
@@ -25,7 +22,7 @@ public class OfflineTokenServlet extends AbstractShowTokensServlet {
 
         if (req.getRequestURI().endsWith("logout")) {
 
-            UriBuilder redirectUriBuilder = UriBuilder.fromUri(OFFLINE_CLIENT_APP_URI);
+            UriBuilder redirectUriBuilder = UriBuilder.fromUri(ServletTestUtils.getUrlBase(req) + "/offline-client");
             if (req.getParameter(OAuth2Constants.SCOPE) != null) {
                 redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, req.getParameter(OAuth2Constants.SCOPE));
             }
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ServletTestUtils.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ServletTestUtils.java
new file mode 100644
index 0000000..0697b5c
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ServletTestUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.adapter.servlet;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.keycloak.common.util.UriUtils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ServletTestUtils {
+
+    // TODO: Couldn't just always read urlBase from req.getRequestURI() ?
+    public static String getUrlBase(HttpServletRequest req) {
+        if (System.getProperty("app.server.ssl.required", "false").equals("true")) {
+            return System.getProperty("app.server.ssl.base.url", "https://localhost:8643");
+        }
+
+        String urlBase = System.getProperty("app.server.base.url");
+
+        if (urlBase == null) {
+            String authServer = System.getProperty("auth.server.container", "auth-server-undertow");
+            if (authServer.contains("undertow")) {
+                urlBase = UriUtils.getOrigin(req.getRequestURL().toString());
+            } else {
+                urlBase = "http://localhost:8280";
+            }
+        }
+
+        return urlBase;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index aded7a5..a0eb15c 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -40,6 +40,9 @@
         <exclude.client>-</exclude.client>
         <!--exclude cluster tests by default, enabled by 'auth-server-*-cluster' profiles in tests/pom.xml-->
         <exclude.cluster>**/cluster/**/*Test.java</exclude.cluster>
+
+        <!-- exclude undertow adapter tests. They can be added by -Dtest=org.keycloak.testsuite.adapter.undertow.**.*Test -->
+        <exclude.undertow.adapter>**/adapter/undertow/**/*Test.java</exclude.undertow.adapter>
     </properties>
     
     <dependencies>
@@ -103,6 +106,7 @@
                         <exclude>${exclude.account}</exclude>
                         <exclude>${exclude.client}</exclude>
                         <exclude>${exclude.cluster}</exclude>
+                        <exclude>${exclude.undertow.adapter}</exclude>
                     </excludes>
                 </configuration>
             </plugin>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java
index dde3c8f..68c9f17 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java
@@ -43,6 +43,10 @@ public class ContainerInfo {
         this.contextRoot = contextRoot;
     }
 
+    public boolean isUndertow() {
+        return getQualifier().toLowerCase().contains("undertow");
+    }
+
     public boolean isAS7() {
         return getQualifier().toLowerCase().contains("as7");
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
index 00deb0a..65cebd6 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
@@ -19,7 +19,10 @@ package org.keycloak.testsuite.arquillian;
 
 import org.apache.tools.ant.DirectoryScanner;
 import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor;
+import org.jboss.arquillian.core.api.InstanceProducer;
+import org.jboss.arquillian.core.api.annotation.Inject;
 import org.jboss.arquillian.test.spi.TestClass;
+import org.jboss.arquillian.test.spi.annotation.ClassScoped;
 import org.jboss.logging.Logger;
 import org.jboss.logging.Logger.Level;
 import org.jboss.shrinkwrap.api.Archive;
@@ -62,6 +65,10 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
     public static final String SAML_ADAPTER_CONFIG_PATH = "/WEB-INF/keycloak-saml.xml";
     public static final String JBOSS_DEPLOYMENT_XML_PATH = "/WEB-INF/jboss-deployment-structure.xml";
 
+    @Inject
+    @ClassScoped
+    private InstanceProducer<TestContext> testContextProducer;
+
     @Override
     public void process(Archive<?> archive, TestClass testClass) {
         log.info("Processing archive " + archive.getName());
@@ -158,6 +165,12 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
 
     public void addFilterDependencies(Archive<?> archive, TestClass testClass) {
         log.info("Adding filter dependencies to " + archive.getName());
+
+        TestContext testContext = testContextProducer.get();
+        if (testContext.getAppServerInfo().isUndertow()) {
+            return;
+        }
+
         String dependency = testClass.getAnnotation(UseServletFilter.class).filterDependency();
         ((WebArchive) archive).addAsLibraries(KeycloakDependenciesResolver.resolveDependencies((dependency + ":" + System.getProperty("project.version"))));
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
index 87950ab..805f1fd 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
@@ -146,7 +146,7 @@ public abstract class AbstractKeycloakTest {
         setDefaultPageUriParameters();
 
         driverSettings();
-        
+
         TestEventsLogger.setDriver(driver);
 
         if (!suiteContext.isAdminPasswordUpdated()) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
index a7e5992..f57b858 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
@@ -126,7 +126,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
 
     @Deployment(name = CustomerPortal.DEPLOYMENT_NAME)
     protected static WebArchive customerPortal() {
-        return servletDeployment(CustomerPortal.DEPLOYMENT_NAME, CustomerServlet.class, ErrorServlet.class);
+        return servletDeployment(CustomerPortal.DEPLOYMENT_NAME, CustomerServlet.class, ErrorServlet.class, ServletTestUtils.class);
     }
 
     @Deployment(name = CustomerPortalNoConf.DEPLOYMENT_NAME)
@@ -156,7 +156,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
 
     @Deployment(name = InputPortal.DEPLOYMENT_NAME)
     protected static WebArchive inputPortal() {
-        return servletDeployment(InputPortal.DEPLOYMENT_NAME, "keycloak.json", InputServlet.class);
+        return servletDeployment(InputPortal.DEPLOYMENT_NAME, "keycloak.json", InputServlet.class, ServletTestUtils.class);
     }
 
     @Deployment(name = TokenMinTTLPage.DEPLOYMENT_NAME)
@@ -194,7 +194,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
 
         assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
-        assertEquals(driver.getCurrentUrl(), inputPortal + "/secured/post");
+        assertCurrentUrlEquals(driver, inputPortal + "/secured/post");
         String pageSource = driver.getPageSource();
         assertTrue(pageSource.contains("parameter=hello"));
 
@@ -641,6 +641,8 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
         String value = "hello";
         Client client = ClientBuilder.newClient();
 
+        //pause(1000000);
+
         Response response = client.target(basicAuthPage
                 .setTemplateValues("mposolda", "password", value).buildUri()).request().get();
 
@@ -797,7 +799,8 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
 
         String serverLogPath = null;
 
-        if (System.getProperty("app.server").equals("wildfly") || System.getProperty("app.server").equals("eap6") || System.getProperty("app.server").equals("eap")) {
+        String appServer = System.getProperty("app.server");
+        if (appServer != null && (appServer.equals("wildfly") || appServer.equals("eap6") || appServer.equals("eap"))) {
             serverLogPath = System.getProperty("app.server.home") + "/standalone/log/server.log";
         }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java
index b8c10b6..e686545 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java
@@ -27,6 +27,7 @@ import java.util.List;
 import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
 import static org.keycloak.testsuite.util.IOUtil.loadRealm;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
 import static org.keycloak.testsuite.util.WaitUtils.pause;
 import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
@@ -49,7 +50,7 @@ public abstract class AbstractOfflineServletsAdapterTest extends AbstractServlet
 
     @Deployment(name = OfflineToken.DEPLOYMENT_NAME)
     protected static WebArchive offlineClient() {
-        return servletDeployment(OfflineToken.DEPLOYMENT_NAME, AdapterActionsFilter.class, AbstractShowTokensServlet.class, OfflineTokenServlet.class, ErrorServlet.class);
+        return servletDeployment(OfflineToken.DEPLOYMENT_NAME, AdapterActionsFilter.class, AbstractShowTokensServlet.class, OfflineTokenServlet.class, ErrorServlet.class, ServletTestUtils.class);
     }
 
     @Override
@@ -92,8 +93,10 @@ public abstract class AbstractOfflineServletsAdapterTest extends AbstractServlet
 
         // Ensure that logout works for webapp (even if offline token will be still valid in Keycloak DB)
         offlineTokenPage.logout();
+        assertCurrentUrlDoesntStartWith(offlineTokenPage);
         loginPage.assertCurrent();
         offlineTokenPage.navigateTo();
+        assertCurrentUrlDoesntStartWith(offlineTokenPage);
         loginPage.assertCurrent();
 
         setAdapterAndServerTimeOffset(0);
@@ -177,6 +180,7 @@ public abstract class AbstractOfflineServletsAdapterTest extends AbstractServlet
 
         //This was necessary to be introduced, otherwise other testcases will fail
         offlineTokenPage.logout();
+        assertCurrentUrlDoesntStartWith(offlineTokenPage);
         loginPage.assertCurrent();
 
         events.clear();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
index a018bdd..a95a00e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
@@ -144,7 +144,7 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
         sessionPortalPage.navigateTo();
         assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
-        assertEquals(driver.getCurrentUrl(), sessionPortalPage.toString());
+        assertCurrentUrlEquals(sessionPortalPage);
         String pageSource = driver.getPageSource();
         assertTrue(pageSource.contains("Counter=1"));
 
@@ -164,7 +164,7 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
         
         // bburke should be still logged with original httpSession in our browser window
         sessionPortalPage.navigateTo();
-        assertEquals(driver.getCurrentUrl(), sessionPortalPage.toString());
+        assertCurrentUrlEquals(sessionPortalPage);
         String pageSource = driver.getPageSource();
         assertTrue(pageSource.contains("Counter=3"));
         String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder())
@@ -186,7 +186,7 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
         sessionPortalPage.navigateTo();
         assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
         login.form().login("bburke@redhat.com", "password");
-        assertEquals(driver.getCurrentUrl(), sessionPortalPage.toString());
+        assertCurrentUrlEquals(sessionPortalPage);
         String pageSource = driver.getPageSource();
         assertTrue(pageSource.contains("Counter=1"));
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowDemoFilterServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowDemoFilterServletAdapterTest.java
new file mode 100644
index 0000000..e910a2a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowDemoFilterServletAdapterTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.adapter.undertow.servlet;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractDemoFilterServletAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@AppServerContainer("auth-server-undertow")
+public class UndertowDemoFilterServletAdapterTest extends AbstractDemoFilterServletAdapterTest {
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowDemoServletsAdapterTest.java
new file mode 100644
index 0000000..c1be0ca
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowDemoServletsAdapterTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.adapter.undertow.servlet;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@AppServerContainer("auth-server-undertow")
+public class UndertowDemoServletsAdapterTest extends AbstractDemoServletsAdapterTest {
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowOfflineServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowOfflineServletsAdapterTest.java
new file mode 100644
index 0000000..517d18a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowOfflineServletsAdapterTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.adapter.undertow.servlet;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractOfflineServletsAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@AppServerContainer("auth-server-undertow")
+public class UndertowOfflineServletsAdapterTest extends AbstractOfflineServletsAdapterTest {
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowSessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowSessionServletAdapterTest.java
new file mode 100644
index 0000000..73c6a22
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowSessionServletAdapterTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.keycloak.testsuite.adapter.undertow.servlet;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractSessionServletAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@AppServerContainer("auth-server-undertow")
+public class UndertowSessionServletAdapterTest extends AbstractSessionServletAdapterTest {
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java
index 1b0f8c0..90ee6ce 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java
@@ -56,6 +56,11 @@ public class URLAssert {
                 currentUrlEqual(driver, page.toString()));
     }
 
+    public static void assertCurrentUrlEquals(WebDriver driver, final String url) {
+        assertTrue("Expected URL: " + url + "; actual: " + driver.getCurrentUrl(),
+                currentUrlEqual(driver, url));
+    }
+
     public static void assertCurrentUrlStartsWith(AbstractPage page) {
         assertCurrentUrlStartsWith(page.getDriver(), page.toString());
     }
diff --git a/travis-run-tests.sh b/travis-run-tests.sh
index 78cf554..2c6ee29 100755
--- a/travis-run-tests.sh
+++ b/travis-run-tests.sh
@@ -6,19 +6,22 @@ fi
 
 if [ $1 == "group1" ]; then
     mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.a**.*Test
+fi
+
+if [ $1 == "group2" ]; then
     mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.b**.*Test
     mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.cli**.*Test
     mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.com**.*Test
 fi
 
-if [ $1 == "group2" ]; then
+if [ $1 == "group3" ]; then
     mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.d**.*Test
     mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.e**.*Test
     mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.f**.*Test
     mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.i**.*Test
 fi
 
-if [ $1 == "group3" ]; then
+if [ $1 == "group4" ]; then
     mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.k**.*Test
     mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.m**.*Test
     mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.o**.*Test