/* * 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(); if (n.getAsset() instanceof ClassAsset) { 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); } } } } }