All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wildfly.httpclient.common.EENamespaceInteroperability Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * 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.server.handlers.PathHandler;
import io.undertow.util.AbstractAttachable;
import io.undertow.util.AttachmentKey;
import io.undertow.util.AttachmentList;
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;
import static org.wildfly.httpclient.common.Protocol.VERSION_ONE_PATH;
import static org.wildfly.httpclient.common.Protocol.VERSION_TWO_PATH;

/**
 * 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 {
    /**
     * Indicates if EE namespace interoperable mode is enabled.
     */
    static final boolean EE_NAMESPACE_INTEROPERABLE_MODE = Boolean.parseBoolean(
            WildFlySecurityManager.getPropertyPrivileged("org.wildfly.ee.namespace.interop", "false"));

    // header indicating the protocol version mode that is being used by the request/response sender
    private static final HttpString PROTOCOL_VERSION = new HttpString("x-wf-version");
    // value for PROTOCOL_VERSION header: used to handshake a higher version, only when both ends use EE jakarta namespace
    private static final String LATEST_VERSION = String.valueOf(Protocol.LATEST);
    // 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 createProtocolVersionHttpHandler(new EENamespaceInteroperabilityHandler(httpHandler), new JakartaNamespaceHandler(httpHandler));
    }

    static HttpHandler createProtocolVersionHttpHandler(HttpHandler interoperabilityHandler, HttpHandler latestProtocolHandler) {
        final PathHandler versionPathHandler = new PathHandler();
        versionPathHandler.addPrefixPath(VERSION_ONE_PATH, interoperabilityHandler);
        versionPathHandler.addPrefixPath(VERSION_TWO_PATH, latestProtocolHandler);
        return versionPathHandler;
    }

    /**
     * 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 {
        private volatile int protocolVersion = -1;

        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
        int getProtocolVersion() {
            return protocolVersion == -1? 1 : protocolVersion;
        }

        @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 {

            private ClientConnectionHolder(ClientConnection connection, URI uri, SSLContext sslContext) {
                super (connection, uri, sslContext);
            }

            @Override
            public void sendRequest(ClientRequest request, ClientCallback callback) {
                switch (protocolVersion) {
                    case -1:
                        // new connection pool: send the protocol version header once with LATEST_VERSION value to see what will be the response
                        request.getRequestHeaders().put(PROTOCOL_VERSION, LATEST_VERSION);
                        request.putAttachment(HTTP_MARSHALLER_FACTORY_KEY, INTEROPERABLE_MARSHALLER_FACTORY);
                        break;
                    case Protocol.JAVAEE_PROTOCOL_VERSION:
                        // connection is Javax EE, so we need to transform class names Javax<->Jakarta
                        request.putAttachment(HTTP_MARSHALLER_FACTORY_KEY, INTEROPERABLE_MARSHALLER_FACTORY);
                        break;
                    case org.wildfly.httpclient.common.Protocol.JAKARTAEE_PROTOCOL_VERSION:
                    default:
                        // connection already set as Jakarta namespace, default factory can be used for marshalling
                        // (no transformation needed)
                        request.getRequestHeaders().put(PROTOCOL_VERSION, LATEST_VERSION);
                        request.putAttachment(HTTP_MARSHALLER_FACTORY_KEY, DEFAULT_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 (protocolVersion == -1) {
                                // we need to check for protocol version header to define the protocol version of the pool
                                if (LATEST_VERSION.equals(response.getResponseHeaders().getFirst(PROTOCOL_VERSION))) {
                                    // this indicates this is the first response server sends, set the protocol to 2
                                    protocolVersion = Protocol.LATEST;
                                    // overwrite previous attachment, no transformation is needed for this connection any more
                                    result.getRequest().putAttachment(HTTP_MARSHALLER_FACTORY_KEY, DEFAULT_FACTORY);
                                } else {
                                    protocolVersion = Protocol.JAVAEE_PROTOCOL_VERSION;
                                    //regarding marsh. factory key, 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> 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 (LATEST_VERSION.equals(exchange.getRequestHeaders().getFirst(PROTOCOL_VERSION))) {
                // respond that this end also supports version two
                exchange.getResponseHeaders().add(PROTOCOL_VERSION, LATEST_VERSION);
                // 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);
            } 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);
        }
    }

    private static class JakartaNamespaceHandler implements HttpHandler {

        private final HttpHandler next;

        JakartaNamespaceHandler(HttpHandler next) {
            this.next = next;
        }

        @Override
        public void handleRequest(HttpServerExchange exchange) throws Exception {
            // no transformation required whatsoever, just make sure we have a factory set
            // or else we will see a NPE when trying to use those attachments
            exchange.putAttachment(HTTP_UNMARSHALLER_FACTORY_KEY, DEFAULT_FACTORY);
            exchange.putAttachment(HTTP_MARSHALLER_FACTORY_KEY, DEFAULT_FACTORY);
            next.handleRequest(exchange);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy