
org.wildfly.httpclient.common.EENamespaceInteroperability Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2022 Red Hat, Inc., and individual 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.wildfly.httpclient.common;
import io.undertow.client.ClientCallback;
import io.undertow.client.ClientConnection;
import io.undertow.client.ClientExchange;
import io.undertow.client.ClientRequest;
import io.undertow.client.ClientResponse;
import io.undertow.client.ContinueNotification;
import io.undertow.client.PushCallback;
import io.undertow.connector.ByteBufferPool;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.AbstractAttachable;
import io.undertow.util.AttachmentKey;
import io.undertow.util.AttachmentList;
import io.undertow.util.HeaderValues;
import io.undertow.util.HttpString;
import org.wildfly.security.manager.WildFlySecurityManager;
import org.xnio.OptionMap;
import org.xnio.XnioWorker;
import org.xnio.channels.StreamSinkChannel;
import org.xnio.channels.StreamSourceChannel;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import static org.jboss.marshalling.ClassNameTransformer.JAVAEE_TO_JAKARTAEE;
import static org.wildfly.httpclient.common.HttpMarshallerFactory.DEFAULT_FACTORY;
/**
* EE namespace interoperability implementation for allowing Jakarta EE namespace servers and clients communication with
* Javax EE namespace endpoints.
*
* EE namespace interoperability must be enabled on all Jakarta servers and clients to make communication
* among them possible.
*
* @author Flavia Rainone
* @author Richard Opalka
*/
final class EENamespaceInteroperability {
// Batavia transformer sensible constant - it can start with either "javax." or "jakarta." if transformation was performed
private static final String VARIABLE_CONSTANT = "jakarta.ejb.FAKE_STRING";
private static final boolean JAKARTAEE_ENVIRONMENT = VARIABLE_CONSTANT.startsWith("jakarta");
/**
* Indicates if EE namespace interoperable mode is enabled.
*/
static final boolean EE_NAMESPACE_INTEROPERABLE_MODE = JAKARTAEE_ENVIRONMENT && Boolean.parseBoolean(
WildFlySecurityManager.getPropertyPrivileged("org.wildfly.ee.namespace.interop", "false"));
// header indicating the ee-namespace mode that is being used by the request/response sender
private static final HttpString EE_NAMESPACE = new HttpString("x-wf-ee-ns");
// value for EE_NAMESPACE header: used only when both ends use EE jakarta namespace
private static final String JAKARTA_EE = "jakarta";
// value for EE_NAMESPACE header: used to indicate that client uses EE jakarta namespace and is on EE namespace interoperability mode
private static final String EE_INTEROP = "interop";
// key used to attach http marshaller factory to a client request / server exchange
private static final AttachmentKey HTTP_MARSHALLER_FACTORY_KEY = AttachmentKey.create(HttpMarshallerFactory.class);
// key used to attach an http unmarshaller factory to a server exchange
private static final AttachmentKey HTTP_UNMARSHALLER_FACTORY_KEY = AttachmentKey.create(HttpMarshallerFactory.class);
// marshaller factory to be used when Javax<->Jakarta transformation is needed
private static final HttpMarshallerFactory INTEROPERABLE_MARSHALLER_FACTORY = new HttpMarshallerFactory(JAVAEE_TO_JAKARTAEE);
static {
if (EE_NAMESPACE_INTEROPERABLE_MODE) {
HttpClientMessages.MESSAGES.javaeeToJakartaeeBackwardCompatibilityLayerInstalled();
}
}
private EENamespaceInteroperability() {}
/**
* Wraps the HTTP server handler into an EE namespace interoperable handler. Such handler implements the
* EE namespace interoperability at the server side before delegating to the wrapped {@code httpHandler}
*
* @param httpHandler the handler to be wrapped
* @return handler the ee namespace interoperability handler
*/
static HttpHandler createInteroperabilityHandler(HttpHandler httpHandler) {
return new EENamespaceInteroperabilityHandler(httpHandler);
}
/**
* Wraps the HTTP server handler into a partial EE namespace interoperable handler. This handler allows non EE namespace
* interoperable servers to respond requests from EE namespace interoperable clients, without all the performance penalty
* paid by interoperable servers.
*
* @param httpHandler the handler to be wrapped
* @return handler the ee namespace partial interoperability handler
*/
static HttpHandler createPartialInteroperabilityHandler(HttpHandler httpHandler) {
return new EENamespacePartialInteroperabilityHandler(httpHandler);
}
/**
* Returns the HTTPMarshallerFactoryProvider instance responsible for taking care of marshalling
* and unmarshalling according to the values negotiated by the ee namespace interoperability headers.
*
* @return the HTTPMarshallerFactoryProvider. All marshalling and unmarshalling done at both server
* and client side have to be done through a factory provided by this object.
*/
static HttpMarshallerFactoryProvider getHttpMarshallerFactoryProvider() {
return new HttpMarshallerFactoryProvider() {
@Override
public HttpMarshallerFactory getMarshallerFactory(AbstractAttachable attachable) {
return attachable.getAttachment(HTTP_MARSHALLER_FACTORY_KEY);
}
@Override
public HttpMarshallerFactory getUnmarshallerFactory(AbstractAttachable attachable) {
return attachable.getAttachment(HTTP_UNMARSHALLER_FACTORY_KEY);
}
};
}
/**
* Returns the HTTP connection pool factory when EE namespace interoperability mode is on. This factory
* creates EE namespace interoperable connections to the server.
*
* @return the {@link HttpConnectionPoolFactory}.
*/
static HttpConnectionPoolFactory getHttpConnectionPoolFactory() {
return (HttpConnectionPool::new);
}
/*
Client side EE namespace interoperability
*/
private static class HttpConnectionPool extends org.wildfly.httpclient.common.HttpConnectionPool {
protected HttpConnectionPool(int maxConnections, int maxStreamsPerConnection, XnioWorker worker, ByteBufferPool byteBufferPool, OptionMap options, HostPool hostPool, long connectionIdleTimeout) {
super(maxConnections, maxStreamsPerConnection, worker, byteBufferPool, options, hostPool, connectionIdleTimeout);
}
@Override
protected org.wildfly.httpclient.common.HttpConnectionPool.ClientConnectionHolder createClientConnectionHolder(ClientConnection connection, URI uri, SSLContext sslContext) {
return new ClientConnectionHolder(connection, uri, sslContext);
}
protected class ClientConnectionHolder extends org.wildfly.httpclient.common.HttpConnectionPool.ClientConnectionHolder {
// keep track if the connection is new
private static final int NEW = 1 << 2;
// indicates this connection belongs to Jakarta EE namespace on both ends and, hence, no class name transformation is needed
private static final int JAKARTA_EE_NS = 1 << 3;
private ClientConnectionHolder(ClientConnection connection, URI uri, SSLContext sslContext) {
super (connection, uri, sslContext);
setFlags(NEW);
}
@Override
public void sendRequest(ClientRequest request, ClientCallback callback) {
if (hasFlags(NEW)) {
// new connection: send the EE namespace header once with interop value to see what will be the response
request.getRequestHeaders().put(EE_NAMESPACE, EE_INTEROP);
request.putAttachment(HTTP_MARSHALLER_FACTORY_KEY, INTEROPERABLE_MARSHALLER_FACTORY);
clearFlags(NEW);
} else if (hasFlags(JAKARTA_EE_NS)) {
// connection already set as Jakarta, default factory can be used for marshalling
// (no transformation needed)
request.getRequestHeaders().put(EE_NAMESPACE, JAKARTA_EE);
request.putAttachment(HTTP_MARSHALLER_FACTORY_KEY, DEFAULT_FACTORY);
} else {
// connection is Javax EE, the only remaining possibility here
// we need to transform class names Javax<->Jakarta
request.putAttachment(HTTP_MARSHALLER_FACTORY_KEY, INTEROPERABLE_MARSHALLER_FACTORY);
}
super.sendRequest(request, new ClientCallback() {
@Override
public void completed(ClientExchange result) {
// wrap the exchange, to handle interoperability at the result (see below)
callback.completed(new EEInteroperableClientExchange(result));
}
@Override
public void failed(IOException e) {
callback.failed(e);
}
});
}
private final class EEInteroperableClientExchange implements ClientExchange {
private final ClientExchange wrappedExchange;
public EEInteroperableClientExchange(ClientExchange clientExchange) {
this.wrappedExchange = clientExchange;
}
@Override
public void setResponseListener(final ClientCallback responseListener) {
wrappedExchange.setResponseListener(new ClientCallback() {
@Override
public void completed(ClientExchange result) {
// this method adds the factory to the request instead of response, this is more efficient
// we prevent adding when jakartaEE is already true and creating a new entry in the response attachment map
final ClientResponse response = result.getResponse();
if (!hasFlags(JAKARTA_EE_NS)) {
// we need to check for EE namespace header for each non-Jakarta response
final HeaderValues serverEENamespace = response.getResponseHeaders().get(EE_NAMESPACE);
if (serverEENamespace != null && serverEENamespace.contains(JAKARTA_EE)) {
// this indicates this is the first response server sends, mark the connection
// as jakarta and be done with it
setFlags(JAKARTA_EE_NS);
// overwrite previous attachment, no transformation is needed for this connection any more
result.getRequest().putAttachment(HTTP_MARSHALLER_FACTORY_KEY, DEFAULT_FACTORY);
} // else: do nothing, the connection is not Jakarta and the marshalling factory provider is already interoperable
} // else: do nothing, request already contains the default marshalling factory
responseListener.completed(result);
}
@Override
public void failed(IOException e) {
responseListener.failed(e);
}
});
}
@Override
public void setContinueHandler(ContinueNotification continueHandler) {
wrappedExchange.setContinueHandler(continueHandler);
}
@Override
public void setPushHandler(PushCallback pushCallback) {
wrappedExchange.setPushHandler(pushCallback);
}
@Override
public StreamSinkChannel getRequestChannel() {
return wrappedExchange.getRequestChannel();
}
@Override
public StreamSourceChannel getResponseChannel() {
return wrappedExchange.getResponseChannel();
}
@Override
public ClientRequest getRequest() {
return wrappedExchange.getRequest();
}
@Override
public ClientResponse getResponse() {
return wrappedExchange.getResponse();
}
@Override
public ClientResponse getContinueResponse() {
return wrappedExchange.getContinueResponse();
}
@Override
public ClientConnection getConnection() {
return wrappedExchange.getConnection();
}
@Override
public T getAttachment(AttachmentKey key) {
return wrappedExchange.getAttachment(key);
}
@Override
public List getAttachmentList(AttachmentKey extends List> key) {
return wrappedExchange.getAttachmentList(key);
}
@Override
public T putAttachment(AttachmentKey key, T value) {
return wrappedExchange.putAttachment(key, value);
}
@Override
public T removeAttachment(AttachmentKey key) {
return wrappedExchange.removeAttachment(key);
}
@Override
public void addToAttachmentList(AttachmentKey> key, T value) {
wrappedExchange.addToAttachmentList(key, value);
}
}
}
}
/*
Server side EE namespace interoperability
*/
private static class EENamespaceInteroperabilityHandler implements HttpHandler {
private final HttpHandler next;
EENamespaceInteroperabilityHandler(HttpHandler next) {
this.next = next;
}
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
if (exchange.getRequestHeaders().contains(EE_NAMESPACE)) {
switch (exchange.getRequestHeaders().getFirst(EE_NAMESPACE)) {
case EE_INTEROP:
exchange.getResponseHeaders().add(EE_NAMESPACE, JAKARTA_EE);
// transformation is required for unmarshalling because client is on EE namespace interoperable mode
exchange.putAttachment(HTTP_UNMARSHALLER_FACTORY_KEY, INTEROPERABLE_MARSHALLER_FACTORY);
// no transformation required for marshalling, server is sending response in Jakarta
exchange.putAttachment(HTTP_MARSHALLER_FACTORY_KEY, DEFAULT_FACTORY);
break;
case JAKARTA_EE:
// no transformation is needed, this is a Jakarta connection at both ends
exchange.putAttachment(HTTP_MARSHALLER_FACTORY_KEY, DEFAULT_FACTORY);
exchange.putAttachment(HTTP_UNMARSHALLER_FACTORY_KEY, DEFAULT_FACTORY);
break;
}
} else {
// transformation is required for unmarshalling request and marshalling response,
// because server is interoperable mode and the lack of a header indicates this is
// either a Javax EE client or a Jakarta EE client that is not interoperable
// the latter case will lead to an error when unmarshalling at client side)
exchange.putAttachment(HTTP_MARSHALLER_FACTORY_KEY, INTEROPERABLE_MARSHALLER_FACTORY);
exchange.putAttachment(HTTP_UNMARSHALLER_FACTORY_KEY, INTEROPERABLE_MARSHALLER_FACTORY);
}
next.handleRequest(exchange);
}
}
// handler that is able to respond to interoperable requests, for servers that are not running on interoperable mode
private static class EENamespacePartialInteroperabilityHandler implements HttpHandler {
private final HttpHandler next;
EENamespacePartialInteroperabilityHandler(HttpHandler next) {
this.next = next;
}
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
if (exchange.getRequestHeaders().contains(EE_NAMESPACE) && exchange.getRequestHeaders().getFirst(EE_NAMESPACE).equals(EE_INTEROP)) {
exchange.getResponseHeaders().add(EE_NAMESPACE, JAKARTA_EE);
// transformation is required for unmarshalling because client is on EE namespace interoperable mode
exchange.putAttachment(HTTP_UNMARSHALLER_FACTORY_KEY, INTEROPERABLE_MARSHALLER_FACTORY);
// no transformation required for marshalling, server is sending response in Jakarta namespace
exchange.putAttachment(HTTP_MARSHALLER_FACTORY_KEY, HttpMarshallerFactory.DEFAULT_FACTORY);
}
// this is not fully interoperable, so we do nothing else, just handle the request
next.handleRequest(exchange);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy