diff --git a/containers/pom.xml b/containers/pom.xml index e0fb55ae33..f42224e193 100644 --- a/containers/pom.xml +++ b/containers/pom.xml @@ -68,6 +68,7 @@ jetty-servlet netty-http simple-http + undertow-http diff --git a/containers/undertow-http/pom.xml b/containers/undertow-http/pom.xml new file mode 100644 index 0000000000..99e1851a01 --- /dev/null +++ b/containers/undertow-http/pom.xml @@ -0,0 +1,99 @@ + + + + + 4.0.0 + + + org.glassfish.jersey.containers + project + 2.26 + + + jersey-container-undertow-http + jar + jersey-container-undertow-http + + Undertow Http Container + + + + io.undertow + undertow-core + + + + + + + com.sun.istack + maven-istack-commons-plugin + true + + + org.codehaus.mojo + build-helper-maven-plugin + true + + + org.apache.felix + maven-bundle-plugin + true + + + + + + ${basedir}/src/main/java + + META-INF/**/* + + + + ${basedir}/src/main/resources + true + + + + + diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainer.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainer.java new file mode 100644 index 0000000000..f1ca12b647 --- /dev/null +++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainer.java @@ -0,0 +1,116 @@ +package org.glassfish.jersey.undertow; + +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.HeaderValues; +import org.glassfish.jersey.internal.MapPropertiesDelegate; +import org.glassfish.jersey.server.ApplicationHandler; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.internal.ContainerUtils; +import org.glassfish.jersey.server.spi.Container; + +import javax.ws.rs.core.Application; +import java.net.URI; +import java.net.URISyntaxException; + +/** + * Undertow {@code Container} implementation based on Undertow's {@link io.undertow.server.HttpHandler}. + * + * @author Jonathan Como (jonathan.como at gmail.com) + */ +public class UndertowHttpContainer implements HttpHandler, Container { + private volatile ApplicationHandler appHandler; + + UndertowHttpContainer(final Application application) { + appHandler = new ApplicationHandler(application); + + // No lifecycle hooks for Undertow's server, so we do this for + // completeness but not accuracy. + appHandler.onStartup(this); + } + + @Override + public ApplicationHandler getApplicationHandler() { + return appHandler; + } + + @Override + public ResourceConfig getConfiguration() { + return appHandler.getConfiguration(); + } + + @Override + public void reload() { + reload(getConfiguration()); + } + + @Override + public void reload(final ResourceConfig configuration) { + appHandler.onShutdown(this); + + appHandler = new ApplicationHandler(configuration); + appHandler.onReload(this); + appHandler.onStartup(this); + } + + @Override + public void handleRequest(final HttpServerExchange exchange) throws Exception { + // If we ware on the IO thread (where the raw HTTP request is processed), + // we want to dispatch to a worker. This is the preferred approach. + if (exchange.isInIoThread()) { + exchange.dispatch(this); + return; + } + + exchange.startBlocking(); + URI baseUri = getBaseUri(exchange); + ContainerRequest request = new ContainerRequest(baseUri, getRequestUri(exchange, baseUri), + exchange.getRequestMethod().toString(), new UndertowSecurityContext(exchange), + new MapPropertiesDelegate()); + + request.setEntityStream(exchange.getInputStream()); + request.setWriter(new UndertowResponseWriter(exchange)); + for (HeaderValues values : exchange.getRequestHeaders()) { + String name = values.getHeaderName().toString(); + for (String value : values) { + request.header(name, value); + } + } + + appHandler.handle(request); + } + + // TODO: No easy way to get the mount point without using Undertow servlets library + private URI getBaseUri(final HttpServerExchange exchange) { + try { + return new URI(exchange.getRequestScheme(), null, exchange.getHostName(), + exchange.getHostPort(), "/", null, null); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } + + private URI getRequestUri(final HttpServerExchange exchange, final URI baseUri) { + String uri = getServerAddress(baseUri) + exchange.getRequestURI(); + String query = exchange.getQueryString(); + if (query != null && !query.isEmpty()) { + uri += "?" + ContainerUtils.encodeUnsafeCharacters(query); + } + + try { + return new URI(uri); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } + + private String getServerAddress(final URI baseUri) { + String serverAddress = baseUri.toString(); + if (serverAddress.charAt(serverAddress.length() - 1) == '/') { + return serverAddress.substring(0, serverAddress.length() - 1); + } + + return serverAddress; + } +} diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerFactory.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerFactory.java new file mode 100644 index 0000000000..fd931a6cba --- /dev/null +++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerFactory.java @@ -0,0 +1,76 @@ +package org.glassfish.jersey.undertow; + +import io.undertow.Undertow; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.spi.Container; +import org.glassfish.jersey.undertow.internal.LocalizationMessages; + +import javax.net.ssl.SSLContext; +import java.net.URI; + +/** + * Factory for creating and starting Undertow server handlers. This returns + * a handle to the started server as {@link Undertow} instances, which allows + * the server to be stopped by invoking the {@link Undertow#stop()} method. + *

+ * To start the server in HTTPS mode an {@link SSLContext} can be provided. + * This will be used to decrypt and encrypt information sent over the + * connected TCP socket channel. + * + * @author Jonathan Como (jonathan.como at gmail.com) + */ +public class UndertowHttpContainerFactory { + public static Undertow createServer(URI uri, ResourceConfig config) { + return createServer(uri, null, config, true); + } + + public static Undertow createServer(URI uri, ResourceConfig config, boolean start) { + return createServer(uri, null, config, start); + } + + public static Undertow createServer(URI uri, SSLContext sslContext, ResourceConfig config) { + return createServer(uri, sslContext, config, true); + } + + public static Undertow createServer(URI uri, SSLContext sslContext, ResourceConfig config, boolean start) { + if (uri == null) { + throw new IllegalArgumentException(LocalizationMessages.URI_CANNOT_BE_NULL()); + } + + int defaultPort; + String scheme = uri.getScheme(); + + if (sslContext != null) { + defaultPort = Container.DEFAULT_HTTPS_PORT; + if (!"https".equals(scheme)) { + throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTPS()); + } + } else { + defaultPort = Container.DEFAULT_HTTP_PORT; + if (!"http".equals(scheme)) { + throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTP()); + } + } + + int port = uri.getPort(); + if (port == -1) { + port = defaultPort; + } + + Undertow.Builder builder = Undertow.builder() + .setHandler(new UndertowHttpContainer(config)); + + if (sslContext != null) { + builder.addHttpsListener(port, uri.getHost(), sslContext); + } else { + builder.addHttpListener(port, uri.getHost()); + } + + Undertow server = builder.build(); + if (start) { + server.start(); + } + + return server; + } +} diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerProvider.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerProvider.java new file mode 100644 index 0000000000..c51ad64fd7 --- /dev/null +++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerProvider.java @@ -0,0 +1,24 @@ +package org.glassfish.jersey.undertow; + +import io.undertow.server.HttpHandler; +import org.glassfish.jersey.server.spi.ContainerProvider; + +import javax.ws.rs.ProcessingException; +import javax.ws.rs.core.Application; + +/** + * Container provider for containers based on Undertow Server {@link io.undertow.Undertow}. + * + * @author Jonathan Como (jonathan.como at gmail.com) + */ +public final class UndertowHttpContainerProvider implements ContainerProvider { + + @Override + public T createContainer(final Class type, final Application application) throws ProcessingException { + if (HttpHandler.class == type || UndertowHttpContainer.class == type) { + return type.cast(new UndertowHttpContainer(application)); + } + + return null; + } +} diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowResponseWriter.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowResponseWriter.java new file mode 100644 index 0000000000..fdff705e1c --- /dev/null +++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowResponseWriter.java @@ -0,0 +1,74 @@ +package org.glassfish.jersey.undertow; + +import io.undertow.server.HttpServerExchange; +import io.undertow.util.HttpString; +import io.undertow.util.StatusCodes; +import org.glassfish.jersey.server.ContainerException; +import org.glassfish.jersey.server.ContainerResponse; +import org.glassfish.jersey.server.spi.ContainerResponseWriter; + +import java.io.OutputStream; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +class UndertowResponseWriter implements ContainerResponseWriter { + private final HttpServerExchange exchange; + + UndertowResponseWriter(final HttpServerExchange exchange) { + this.exchange = exchange; + } + + @Override + public OutputStream writeResponseStatusAndHeaders( + final long contentLength, + final ContainerResponse context) throws ContainerException { + + exchange.setStatusCode(context.getStatus()); + + Map> headers = context.getStringHeaders(); + for (Map.Entry> e : headers.entrySet()) { + HttpString name = new HttpString(e.getKey()); + exchange.getResponseHeaders().addAll(name, e.getValue()); + } + + return exchange.getOutputStream(); + } + + @Override + public boolean suspend(final long timeOut, final TimeUnit timeUnit, final TimeoutHandler timeoutHandler) { + return false; + } + + @Override + public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit) throws IllegalStateException { + } + + @Override + public void commit() { + exchange.endExchange(); + } + + @Override + public void failure(final Throwable error) { + if (!exchange.isResponseStarted()) { + exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); + exchange.getResponseSender().send("Internal Server Error"); + } + + commit(); + + // Rethrow the original exception as required by JAX-RS, 3.3.4. + if (error instanceof RuntimeException) { + throw (RuntimeException) error; + } else { + throw new ContainerException(error); + } + } + + @Override + public boolean enableResponseBuffering() { + return false; + } +} + diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowSecurityContext.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowSecurityContext.java new file mode 100644 index 0000000000..5aec980128 --- /dev/null +++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowSecurityContext.java @@ -0,0 +1,49 @@ +package org.glassfish.jersey.undertow; + +import io.undertow.security.idm.Account; +import io.undertow.server.HttpServerExchange; + +import javax.ws.rs.core.SecurityContext; +import java.security.Principal; + +class UndertowSecurityContext implements SecurityContext { + private final HttpServerExchange exchange; + + UndertowSecurityContext(final HttpServerExchange exchange) { + this.exchange = exchange; + } + + @Override + public Principal getUserPrincipal() { + Account account = getAccount(); + if (account != null) { + return account.getPrincipal(); + } + + return null; + } + + @Override + public boolean isUserInRole(final String role) { + Account account = getAccount(); + return account != null && account.getRoles().contains(role); + } + + @Override + public boolean isSecure() { + return exchange.isSecure(); + } + + @Override + public String getAuthenticationScheme() { + return exchange.getSecurityContext().getMechanismName(); + } + + private Account getAccount() { + if (exchange.getSecurityContext() != null) { + return exchange.getSecurityContext().getAuthenticatedAccount(); + } + + return null; + } +} diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/package-info.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/package-info.java new file mode 100644 index 0000000000..3795102dea --- /dev/null +++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/package-info.java @@ -0,0 +1,44 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2011-2017 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://oss.oracle.com/licenses/CDDL+GPL-1.1 + * or LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ + +/** + * Jersey Undertow container classes. + */ +package org.glassfish.jersey.undertow; diff --git a/containers/undertow-http/src/main/resources/META-INF.services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/undertow-http/src/main/resources/META-INF.services/org.glassfish.jersey.server.spi.ContainerProvider new file mode 100644 index 0000000000..ddcacdf3f5 --- /dev/null +++ b/containers/undertow-http/src/main/resources/META-INF.services/org.glassfish.jersey.server.spi.ContainerProvider @@ -0,0 +1 @@ +org.glassfish.jersey.undertow.UndertowHttpContainerProvider diff --git a/containers/undertow-http/src/main/resources/org.glassfish.jersey.undertow.internal/localization.properties b/containers/undertow-http/src/main/resources/org.glassfish.jersey.undertow.internal/localization.properties new file mode 100644 index 0000000000..abba9ec2ea --- /dev/null +++ b/containers/undertow-http/src/main/resources/org.glassfish.jersey.undertow.internal/localization.properties @@ -0,0 +1,43 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright (c) 2013-2017 Oracle and/or its affiliates. All rights reserved. +# +# The contents of this file are subject to the terms of either the GNU +# General Public License Version 2 only ("GPL") or the Common Development +# and Distribution License("CDDL") (collectively, the "License"). You +# may not use this file except in compliance with the License. You can +# obtain a copy of the License at +# https://oss.oracle.com/licenses/CDDL+GPL-1.1 +# or LICENSE.txt. See the License for the specific +# language governing permissions and limitations under the License. +# +# When distributing the software, include this License Header Notice in each +# file and include the License file at LICENSE.txt. +# +# GPL Classpath Exception: +# Oracle designates this particular file as subject to the "Classpath" +# exception as provided by Oracle in the GPL Version 2 section of the License +# file that accompanied this code. +# +# Modifications: +# If applicable, add the following below the License Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyright [year] [name of copyright owner]" +# +# Contributor(s): +# If you wish your version of this file to be governed by only the CDDL or +# only the GPL Version 2, indicate your decision by adding "[Contributor] +# elects to include this software in this distribution under the [CDDL or GPL +# Version 2] license." If you don't indicate a single choice of license, a +# recipient has the option to distribute your version of this file under +# either the CDDL, the GPL Version 2 or to extend the choice of license to +# its licensees as provided above. However, if you add GPL Version 2 code +# and therefore, elected the GPL Version 2 license, then the option applies +# only if the new code is made subject to such option by the copyright +# holder. +# + +uri.cannot.be.null=The URI must not be null. +wrong.scheme.when.using.http=The URI scheme should be 'http' when not using SSL. +wrong.scheme.when.using.https=The URI scheme should be 'https' when using SSL. diff --git a/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/AbstractUndertowServerTester.java b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/AbstractUndertowServerTester.java new file mode 100644 index 0000000000..7114b63524 --- /dev/null +++ b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/AbstractUndertowServerTester.java @@ -0,0 +1,86 @@ +package org.glassfish.jersey.undertow; + +import io.undertow.Undertow; +import org.glassfish.jersey.internal.util.PropertiesHelper; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.junit.After; + +import javax.ws.rs.core.UriBuilder; +import java.net.URI; +import java.security.AccessController; +import java.util.logging.Level; +import java.util.logging.Logger; + +public abstract class AbstractUndertowServerTester { + private static final Logger LOGGER = Logger.getLogger(AbstractUndertowServerTester.class.getName()); + private static final int DEFAULT_PORT = 9998; + + /** + * Get the port to be used for test application deployments. + * + * @return The HTTP port of the URI + */ + private int getPort() { + final String value = AccessController + .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port")); + if (value != null) { + + try { + final int i = Integer.parseInt(value); + if (i <= 0) { + throw new NumberFormatException("Value not positive."); + } + return i; + } catch (NumberFormatException e) { + LOGGER.log(Level.CONFIG, + "Value of 'jersey.config.test.container.port'" + + " property is not a valid positive integer [" + value + "]." + + " Reverting to default [" + DEFAULT_PORT + "].", + e); + } + } + return DEFAULT_PORT; + } + + private volatile Undertow server; + + UriBuilder getUri() { + return UriBuilder.fromUri("http://localhost").port(getPort()); + } + + void startServer(Class... resources) { + ResourceConfig config = new ResourceConfig(resources); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + final URI baseUri = getBaseUri(); + server = UndertowHttpContainerFactory.createServer(baseUri, config); + LOGGER.log(Level.INFO, "Undertow-http server started on base uri: " + baseUri); + } + + void startServer(ResourceConfig config) { + final URI baseUri = getBaseUri(); + server = UndertowHttpContainerFactory.createServer(baseUri, config); + LOGGER.log(Level.INFO, "Undertow-http server started on base uri: " + baseUri); + } + + private URI getBaseUri() { + return UriBuilder.fromUri("http://localhost/").port(getPort()).build(); + } + + private void stopServer() { + try { + server.stop(); + server = null; + LOGGER.log(Level.INFO, "Undertow-http server stopped."); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @After + public void tearDown() { + if (server != null) { + stopServer(); + } + } +} diff --git a/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/BasicRequestTest.java b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/BasicRequestTest.java new file mode 100644 index 0000000000..b8c42cbf8f --- /dev/null +++ b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/BasicRequestTest.java @@ -0,0 +1,67 @@ +package org.glassfish.jersey.undertow; + +import org.junit.Before; +import org.junit.Test; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.net.URI; + +import static org.junit.Assert.assertEquals; + +public class BasicRequestTest extends AbstractUndertowServerTester { + + @Path("/") + @Produces(MediaType.TEXT_PLAIN) + public static class TestResource { + + @GET + @Path("/dummy") + public String dummyResponse(@QueryParam("answer") String answer) { + return answer; + } + + @GET + @Path("/exception/{status}") + public void exceptionResponse(@PathParam("status") int status) { + throw new WebApplicationException(status); + } + } + + @Before + public void setup() { + startServer(TestResource.class); + } + + @Test + public void test400StatusCode() { + Client client = ClientBuilder.newClient(); + WebTarget target = client.target(getUri().path("exception/400").build()); + assertEquals(400, target.request().get(Response.class).getStatus()); + } + + @Test + public void test500StatusCode() { + Client client = ClientBuilder.newClient(); + WebTarget target = client.target(getUri().path("exception/500").build()); + assertEquals(500, target.request().get(Response.class).getStatus()); + } + + @Test + public void testValidResponse() { + String answer = "Tested, working."; + Client client = ClientBuilder.newClient(); + URI uri = getUri().path("dummy").queryParam("answer", answer).build(); + WebTarget target = client.target(uri); + assertEquals(answer, target.request().get(String.class)); + } +} \ No newline at end of file diff --git a/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/LifecycleListenerTest.java b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/LifecycleListenerTest.java new file mode 100644 index 0000000000..33ee7a841b --- /dev/null +++ b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/LifecycleListenerTest.java @@ -0,0 +1,54 @@ +package org.glassfish.jersey.undertow; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener; +import org.glassfish.jersey.server.spi.Container; +import org.junit.Test; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; + +import static org.junit.Assert.assertEquals; + +public class LifecycleListenerTest extends AbstractUndertowServerTester { + + @Path("/") + public static class TestResource { + @GET + public void doNothing() { + } + } + + public static class Reloader extends AbstractContainerLifecycleListener { + Container container; + + public void reload(ResourceConfig newConfig) { + container.reload(newConfig); + } + + @Override + public void onStartup(Container container) { + this.container = container; + } + } + + @Test + public void testReload() throws Exception { + Reloader reloader = new Reloader(); + ResourceConfig config = new ResourceConfig(); + config.registerInstances(reloader); + + startServer(config); + + Client client = ClientBuilder.newClient(); + WebTarget target = client.target(getUri().path("/").build()); + assertEquals(404, target.request().get(Response.class).getStatus()); + + reloader.reload(new ResourceConfig(TestResource.class)); + assertEquals(204, target.request().get(Response.class).getStatus()); + } +} diff --git a/pom.xml b/pom.xml index f42ccf6991..991ab12ba4 100644 --- a/pom.xml +++ b/pom.xml @@ -1421,6 +1421,12 @@ ${simple.version} + + io.undertow + undertow-core + ${undertow.version} + + org.codehaus.jettison jettison @@ -1908,6 +1914,7 @@ 6.0.1 1.7.12 4.3.4.RELEASE + 1.4.21.Final 5.1.3.Final 2.2.14.Final 3.0.0.Final