TyrusServerEndpointConfigurator.java

/*
 * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.tyrus.core;

import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;

import javax.websocket.Extension;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;

import org.glassfish.tyrus.core.collection.LazyValue;
import org.glassfish.tyrus.core.collection.Value;
import org.glassfish.tyrus.core.collection.Values;
import org.glassfish.tyrus.core.extension.ExtendedExtension;
import org.glassfish.tyrus.core.frame.Frame;

/**
 * Tyrus' implementation of {@link ServerEndpointConfig.Configurator}.
 *
 * @author Pavel Bucek
 */
public class TyrusServerEndpointConfigurator extends ServerEndpointConfig.Configurator {

    private LazyValue<ComponentProviderService> componentProviderService;

    public TyrusServerEndpointConfigurator() {
        this.componentProviderService = Values.lazy(new Value<ComponentProviderService>() {
            @Override
            public ComponentProviderService get() {
                return ComponentProviderService.create();
            }
        });
    }

    @Override
    public String getNegotiatedSubprotocol(List<String> supported, List<String> requested) {
        if (requested != null) {
            for (String clientProtocol : requested) {
                if (supported.contains(clientProtocol)) {
                    return clientProtocol;
                }
            }
        }

        return "";
    }

    @Override
    public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested) {
        installed = new ArrayList<Extension>(installed);

        List<Extension> result = new ArrayList<Extension>();

        if (requested != null) {
            for (final Extension requestedExtension : requested) {
                for (Extension extension : installed) {
                    final String name = extension.getName();

                    if (name != null && name.equals(requestedExtension.getName())) {

                        /*
                         * Per message compression - draft 19
                         * https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19
                         *
                         * Extensions header can contain multiple declarations of the same extension with various
                         * parameters. The result have to contain only one result; depends on servers choice.
                         *
                         * {@see ExtendedExtension#onExtensionNegotiation(ExtendedExtension.ExtensionContext, List)}
                         */
                        boolean alreadyAdded = false;

                        for (Extension e : result) {
                            if (e.getName().equals(name)) {
                                alreadyAdded = true;
                            }
                        }

                        if (!alreadyAdded) {
                            if (extension instanceof ExtendedExtension) {
                                final ExtendedExtension extendedExtension = (ExtendedExtension) extension;
                                result.add(new ExtendedExtension() {
                                    @Override
                                    public Frame processIncoming(ExtensionContext context, Frame frame) {
                                        return extendedExtension.processIncoming(context, frame);
                                    }

                                    @Override
                                    public Frame processOutgoing(ExtensionContext context, Frame frame) {
                                        return extendedExtension.processOutgoing(context, frame);
                                    }

                                    /**
                                     * {@inheritDoc}
                                     * <p/>
                                     * Please note the TODO. {@link
                                     * ExtendedExtension#onExtensionNegotiation(ExtensionContext, List)}
                                     */
                                    @Override
                                    public List<Parameter> onExtensionNegotiation(ExtensionContext context,
                                                                                  List<Parameter> requestedParameters) {
                                        return extendedExtension
                                                .onExtensionNegotiation(context, requestedExtension.getParameters());
                                    }

                                    @Override
                                    public void onHandshakeResponse(ExtensionContext context,
                                                                    List<Parameter> responseParameters) {
                                        extendedExtension.onHandshakeResponse(context, responseParameters);
                                    }

                                    @Override
                                    public void destroy(ExtensionContext context) {
                                        extendedExtension.destroy(context);
                                    }

                                    @Override
                                    public String getName() {
                                        return name;
                                    }

                                    @Override
                                    public List<Parameter> getParameters() {
                                        return extendedExtension.getParameters();
                                    }
                                });
                            } else {
                                result.add(requestedExtension);
                            }
                        }
                    }
                }
            }
        }

        return result;
    }

    @Override
    public boolean checkOrigin(String originHeaderValue) {
        return true;
    }

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
    }

    @Override
    public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
        //noinspection unchecked
        return (T) componentProviderService.get().getEndpointInstance(endpointClass);
    }

    /**
     * Check whether the user defined {@link ServerEndpointConfig.Configurator} has overridden
     * {@link ServerEndpointConfig.Configurator#getEndpointInstance(Class)} method.
     * In that case, CDIProvider does not manage the instantiation.
     * @param configurator The user defined {@link ServerEndpointConfig.Configurator} subclass
     * @param <T> The subclass type
     * @return {@code true} iff the user creates the endpoint instance on their own.
     */
    static <T extends ServerEndpointConfig.Configurator> boolean overridesGetEndpointInstance(T configurator) {
        final String getInstanceName = "getEndpointInstance";
        if (null == configurator) {
            return false;
        }
        try {
            final Method originalMethod = TyrusServerEndpointConfigurator.class.isInstance(configurator)
                    ? TyrusServerEndpointConfigurator.class.getMethod(getInstanceName, Class.class)
                    : ServerEndpointConfig.Configurator.class.getMethod(getInstanceName, Class.class);
            final Method configMethod = configurator.getClass().getMethod(getInstanceName, Class.class);
            return !originalMethod.equals(configMethod);
        } catch (NoSuchMethodException e) {
            return false;
        }
    }
}