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

org.glassfish.tyrus.platform.EndpointWrapper Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2011 - 2012 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
 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/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 packager/legal/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.
 */

package org.glassfish.tyrus.platform;

import org.glassfish.tyrus.platform.utils.PrimitivesToBoxing;
import org.glassfish.tyrus.spi.SPIEndpoint;
import org.glassfish.tyrus.spi.SPIHandshakeRequest;

import javax.net.websocket.CloseReason;
import javax.net.websocket.DecodeException;
import javax.net.websocket.Decoder;
import javax.net.websocket.EncodeException;
import javax.net.websocket.Encoder;
import javax.net.websocket.Endpoint;
import javax.net.websocket.EndpointConfiguration;
import javax.net.websocket.MessageHandler;
import javax.net.websocket.RemoteEndpoint;
import javax.net.websocket.Session;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Wrapps the registered application class.
 * There is one {@link EndpointWrapper} for each application class, which handles all the methods.
 *
 * @author dannycoward
 * @author Stepan Kopriva (stepan.kopriva at oracle.com)
 */
public class EndpointWrapper extends SPIEndpoint {

    /**
     * Server configuration.
     */
    private EndpointConfiguration configuration;

    /**
     * Path relative to the servlet context path
     */
    private final String path;

    /**
     * Model representing the annotated bean class.
     */
    private Model model;

    /**
     * Remote endpoints for the {@link Endpoint} represented by this wrapper.
     */
    private ConcurrentHashMap wrappers
            = new ConcurrentHashMap();

    /**
     * The corresponding {@link Endpoint} was created from annotated class / instance.
     */
    private boolean annotated;

    /**
     * Creates new endpoint wrapper.
     *
     * @param path  address of this endpoint as annotated by {@link javax.net.websocket.annotations.WebSocketEndpoint} annotation.
     * @param model model of the application class.
     */
    public EndpointWrapper(String path, Model model, EndpointConfiguration configuration) {
        this.path = path;
        this.model = model;
        this.configuration = configuration;
        this.annotated = model.wasAnnotated();
    }

    /**
     * Checks whether the provided dynamicPath matches with this endpoint, i.e. if there is a method that can process the request.
     *
     * @param remoteUri   path to be checked.
     * @param dynamicPath taken from the {@link javax.net.websocket.annotations.WebSocketMessage} method annotation.
     * @return {@code true} if the paths match, {@code false} otherwise.
     */
    protected boolean doesPathMatch(String remoteUri, String dynamicPath) {
        if (dynamicPath.equals("*")) {
            return true;
        } else if ((path + dynamicPath).equals(remoteUri)) {
            return true;
        }
        return false;
    }

    private Object decodeMessage(Object message, Class type, boolean isString) throws DecodeException {
        for (Decoder dec : configuration.getDecoders()) {
            try {
                List interfaces = Arrays.asList(dec.getClass().getInterfaces());

                if (isString && interfaces.contains(Decoder.Text.class)) {
                    Method m = dec.getClass().getDeclaredMethod("decode", String.class);
                    if (type != null && type.equals(m.getReturnType())) {
                        if (((Decoder.Text) dec).willDecode((String) message)) {
                            return ((Decoder.Text) dec).decode((String) message);
                        }
                    }
                } else if (!isString && interfaces.contains(Decoder.Binary.class)) {
                    Method m = dec.getClass().getDeclaredMethod("decode", ByteBuffer.class);
                    if (type != null && type.equals(m.getReturnType())) {
                        if (((Decoder.Binary) dec).willDecode((ByteBuffer) message)) {
                            return ((Decoder.Binary) dec).decode((ByteBuffer) message);
                        }
                    }
                }
            } catch (DecodeException de) {
                de.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    String doEncode(Object o) throws EncodeException {
        for (Encoder enc : configuration.getEncoders()) {
            List interfaces = Arrays.asList(enc.getClass().getInterfaces());
            if (interfaces.contains(Encoder.Text.class)) {
                try {
                    Method m = enc.getClass().getMethod("encode", o.getClass());
                    if (m != null) {
                        return ((Encoder.Text) enc).encode(o);
                    }
                } catch (java.lang.NoSuchMethodException nsme) {
                    // just continue looping
                } catch (EncodeException ee) {
                    ee.printStackTrace();
                    throw ee;
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }

        throw new EncodeException("Unable to encode ", o);
    }

    String getPath() {
        return this.path;
    }

    @Override
    public boolean checkHandshake(SPIHandshakeRequest hr) {
        return hr.getRequestURI().startsWith(path);
    }

    @Override
    public List getSupportedProtocols(List subProtocol) {
        List supported = new ArrayList();
        for (String nextRequestedProtocolName : subProtocol) {
            if (model.getSubprotocols().contains(nextRequestedProtocolName)) {
                supported.add(nextRequestedProtocolName);
            }
        }
        return supported;
    }

    @Override
    public void remove() {

    }

    @Override
    public void onConnect(RemoteEndpoint gs) {
        RemoteEndpointWrapper peer = getPeer(gs);
//        peer.setAddress(gs.getUri()); TODO set the correct address!
        peer.setAddress(null);
        this.onGeneratedBeanConnect(peer);
    }

    @Override
    public void onMessage(RemoteEndpoint gs, ByteBuffer messageBytes) {
        RemoteEndpointWrapper peer = getPeer(gs);
        peer.updateLastConnectionActivity();
        processCompleteMessage(gs, messageBytes, false);
    }

    @Override
    public void onMessage(RemoteEndpoint gs, String messageString) {
        RemoteEndpointWrapper peer = getPeer(gs);
        peer.updateLastConnectionActivity();
        processCompleteMessage(gs, messageString, true);
    }

    /**
     * Processes just messages that come in one part, i.e. not streamed messages.
     *
     * @param gs       message sender.
     * @param o        message.
     * @param isString String / byte[] message.
     */
    private void processCompleteMessage(RemoteEndpoint gs, Object o, boolean isString) {
        RemoteEndpointWrapper peer = getPeer(gs);
        boolean decoded = false;

        for (MessageHandler handler : (Set) peer.getSession().getMessageHandlers()) {
            if (isString) {
                if (handler instanceof MessageHandler.Text) {
                    ((MessageHandler.Text) handler).onMessage((String) o);
                    decoded = true;
                }
            } else {
                if (handler instanceof MessageHandler.Binary) {
                    ((MessageHandler.Binary) handler).onMessage((ByteBuffer) o);
                    decoded=true;
                }
            }
        }

        try {
            for (Method m : model.getOnMessageMethods()) {
                Class[] paramTypes = m.getParameterTypes();
                Class type = null;

                for (Class methodParamType : paramTypes) {
                    if (!methodParamType.equals(Session.class)) {
                        type = (PrimitivesToBoxing.getBoxing(methodParamType) == null) ? methodParamType : PrimitivesToBoxing.getBoxing(methodParamType);
                        break;
                    }
                }

                Object decodedMessageObject = this.decodeMessage(o, type, isString);

                if (decodedMessageObject != null) {
                    Object returned = invokeMethod(decodedMessageObject, m, peer);

                    if (returned != null) {
                        if (o instanceof String) {
                            String messageToSendAsString = this.doEncode(returned);
                            peer.sendString(messageToSendAsString);
                        } else if (returned instanceof byte[]) {
                            peer.sendBytes((ByteBuffer) returned);
                        }
                    }
                    decoded = true;
                }
            }

            if (!decoded) {
                throw new Exception();
            }

        } catch (IOException ioe) {
            this.handleGeneratedBeanException(peer, ioe);
        } catch (DecodeException de) {
            this.handleGeneratedBeanException(peer, de);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException("Error invoking message decoding");
        }
    }

    private Object invokeMethod(Object object, Method method, RemoteEndpointWrapper peer) throws Exception {
        Object result;
        Class[] paramTypes = method.getParameterTypes();
        int noOfParameters = paramTypes.length;
        Object param0 = model.getBean();
        Object param1, param2;

        if (!parameterBelongsToMethod(method, object)) {
            return null;
        }

        if (paramTypes[0].equals(object.getClass()) ||
                PrimitivesToBoxing.getBoxing(paramTypes[0]).equals(object.getClass())) {
            param1 = object;
            param2 = peer.getSession();
        } else {
            param1 = peer.getSession();
            param2 = object;
        }

        switch (noOfParameters) {
            case 1:
                result = method.invoke(param0, param1);
                break;
            case 2:
                result = method.invoke(param0, param1, param2);
                break;
            default:
                throw new RuntimeException("can't deal with " + noOfParameters + " parameters.");
        }

        return result;
    }

    private boolean parameterBelongsToMethod(Method method, Object parameter) {
        Class[] paramTypes = method.getParameterTypes();

        for (Class paramType : paramTypes) {
            if (PrimitivesToBoxing.getBoxing(paramType).equals(parameter.getClass())) {
                return true;
            }
        }

        return false;
    }

    @Override
    public void onClose(RemoteEndpoint gs) {
        RemoteEndpointWrapper wsw = getPeer(gs);
        this.onGeneratedBeanClose(wsw);
        wrappers.remove(gs);
    }

    public void handleGeneratedBeanException(RemoteEndpoint peer, Exception e) {
        e.printStackTrace();
        throw new RuntimeException("Error handling not supported yet.");
//        for (Method m : model.getOnErrorMethods()) {
//            try {
//                System.out.println("Error replying to client " + e.getMessage());
//                e.printStackTrace();
//                m.invoke(model.getBean(), e, peer);
//            } catch (Exception ex) {
//                ex.printStackTrace();
//                throw new RuntimeException("Error invoking it.");
//            }
//        }
    }

    public void onGeneratedBeanConnect(RemoteEndpointWrapper peer) {
        if (annotated) {

            for (Method m : model.getOnOpenMethods()) {
                try {
                    m.invoke(model.getBean(), peer.getSession());
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException("Error invoking it.");
                }
            }

        } else if (model.getBean() instanceof Endpoint) {
            ((Endpoint) model.getBean()).onOpen(peer.getSession());
        }else{
            try {
                throw new Exception("onConnect could not be invoked.");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void onGeneratedBeanClose(RemoteEndpointWrapper peer) {
        if (annotated) {
            for (Method m : model.getOnCloseMethods()) {
                try {
                    m.invoke(model.getBean(), peer.getSession());
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException("Error invoking it.");
                }
            }
        } else if (model.getBean() instanceof Endpoint) {
            ((Endpoint) model.getBean()).onClose(peer.getSession(), new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Normal Closure."));
        }else{
            try {
                throw new Exception("onClose could not be invoked.");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @SuppressWarnings("unchecked")
    protected final RemoteEndpointWrapper getPeer(RemoteEndpoint gs) {
        RemoteEndpointWrapper result = wrappers.get(gs);

        if (result == null) {
            result = RemoteEndpointWrapper.getRemoteWrapper(gs, this);
        }

        wrappers.put(gs, result);
        return result;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy