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

org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory Maven / Gradle / Ivy

There is a newer version: 12.0.13
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.websocket.common;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.websocket.api.Callback;
import org.eclipse.jetty.websocket.api.Frame;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketContainer;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.api.exceptions.InvalidWebSocketException;
import org.eclipse.jetty.websocket.common.internal.ByteBufferMessageSink;
import org.eclipse.jetty.websocket.common.internal.PartialByteBufferMessageSink;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException;
import org.eclipse.jetty.websocket.core.messages.InputStreamMessageSink;
import org.eclipse.jetty.websocket.core.messages.PartialStringMessageSink;
import org.eclipse.jetty.websocket.core.messages.ReaderMessageSink;
import org.eclipse.jetty.websocket.core.messages.StringMessageSink;
import org.eclipse.jetty.websocket.core.util.InvokerUtils;
import org.eclipse.jetty.websocket.core.util.ReflectUtils;

/**
 * Factory to create {@link JettyWebSocketFrameHandler} instances suitable for
 * use with jetty-native websocket API.
 * 

* Will create a {@link org.eclipse.jetty.websocket.core.FrameHandler} suitable for use with classes/objects that: *

*
    *
  • Is @{@link org.eclipse.jetty.websocket.api.annotations.WebSocket} annotated
  • *
  • Implements {@link org.eclipse.jetty.websocket.api.Session.Listener}
  • *
*/ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle { private static final InvokerUtils.Arg[] textCallingArgs = new InvokerUtils.Arg[]{ new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(String.class).required() }; private static final InvokerUtils.Arg[] binaryBufferCallingArgs = new InvokerUtils.Arg[]{ new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(ByteBuffer.class).required(), new InvokerUtils.Arg(Callback.class).required() }; private static final InvokerUtils.Arg[] inputStreamCallingArgs = new InvokerUtils.Arg[]{ new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(InputStream.class).required() }; private static final InvokerUtils.Arg[] readerCallingArgs = new InvokerUtils.Arg[]{ new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(Reader.class).required() }; private static final InvokerUtils.Arg[] textPartialCallingArgs = new InvokerUtils.Arg[]{ new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(String.class).required(), new InvokerUtils.Arg(boolean.class).required() }; private static final InvokerUtils.Arg[] binaryPartialBufferCallingArgs = new InvokerUtils.Arg[]{ new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(ByteBuffer.class).required(), new InvokerUtils.Arg(boolean.class).required(), new InvokerUtils.Arg(Callback.class).required() }; private final WebSocketContainer container; private final WebSocketComponents components; private final Map, JettyWebSocketFrameHandlerMetadata> metadataMap = new ConcurrentHashMap<>(); public JettyWebSocketFrameHandlerFactory(WebSocketContainer container, WebSocketComponents components) { this.container = container; this.components = components; } public WebSocketComponents getWebSocketComponents() { return components; } public JettyWebSocketFrameHandlerMetadata getMetadata(Class endpointClass) { JettyWebSocketFrameHandlerMetadata metadata = metadataMap.get(endpointClass); if (metadata == null) { metadata = createMetadata(endpointClass); metadataMap.put(endpointClass, metadata); } return metadata; } public JettyWebSocketFrameHandlerMetadata createMetadata(Class endpointClass) { if (Session.Listener.class.isAssignableFrom(endpointClass)) return createListenerMetadata(endpointClass); WebSocket websocket = endpointClass.getAnnotation(WebSocket.class); if (websocket != null) return createAnnotatedMetadata(websocket, endpointClass); throw new InvalidWebSocketException("Unrecognized WebSocket endpoint: " + endpointClass.getName()); } public JettyWebSocketFrameHandler newJettyFrameHandler(Object endpointInstance) { JettyWebSocketFrameHandlerMetadata metadata = getMetadata(endpointInstance.getClass()); // Decorate the endpointInstance while we are still upgrading for access to things like HttpSession. components.getObjectFactory().decorate(endpointInstance); return new JettyWebSocketFrameHandler(container, endpointInstance, metadata); } private MethodHandle toMethodHandle(MethodHandles.Lookup lookup, Method method) { try { return lookup.unreflect(method); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to access method " + method, e); } } private JettyWebSocketFrameHandlerMetadata createListenerMetadata(Class endpointClass) { JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata(); metadata.setAutoDemand(Session.Listener.AutoDemanding.class.isAssignableFrom(endpointClass)); MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass); Method openMethod = findMethod(endpointClass, "onWebSocketOpen", Session.class); if (openMethod != null) { MethodHandle connectHandle = toMethodHandle(lookup, openMethod); metadata.setOpenHandle(connectHandle, openMethod); } Method frameMethod = findMethod(endpointClass, "onWebSocketFrame", Frame.class, Callback.class); if (frameMethod != null) { MethodHandle frameHandle = toMethodHandle(lookup, frameMethod); metadata.setFrameHandle(frameHandle, frameMethod); } Method pingMethod = findMethod(endpointClass, "onWebSocketPing", ByteBuffer.class); if (pingMethod != null) { MethodHandle pingHandle = toMethodHandle(lookup, pingMethod); metadata.setPingHandle(pingHandle, pingMethod); } Method pongMethod = findMethod(endpointClass, "onWebSocketPong", ByteBuffer.class); if (pongMethod != null) { MethodHandle pongHandle = toMethodHandle(lookup, pongMethod); metadata.setPongHandle(pongHandle, pongMethod); } Method partialTextMethod = findMethod(endpointClass, "onWebSocketPartialText", String.class, boolean.class); if (partialTextMethod != null) { MethodHandle partialTextHandle = toMethodHandle(lookup, partialTextMethod); metadata.setTextHandle(PartialStringMessageSink.class, partialTextHandle, partialTextMethod); } Method partialBinaryMethod = findMethod(endpointClass, "onWebSocketPartialBinary", ByteBuffer.class, boolean.class, Callback.class); if (partialBinaryMethod != null) { MethodHandle partialBinaryHandle = toMethodHandle(lookup, partialBinaryMethod); metadata.setBinaryHandle(PartialByteBufferMessageSink.class, partialBinaryHandle, partialBinaryMethod); } Method textMethod = findMethod(endpointClass, "onWebSocketText", String.class); if (textMethod != null) { MethodHandle textHandle = toMethodHandle(lookup, textMethod); metadata.setTextHandle(StringMessageSink.class, textHandle, textMethod); } Method binaryMethod = findMethod(endpointClass, "onWebSocketBinary", ByteBuffer.class, Callback.class); if (binaryMethod != null) { MethodHandle binaryHandle = toMethodHandle(lookup, binaryMethod); metadata.setBinaryHandle(ByteBufferMessageSink.class, binaryHandle, binaryMethod); } Method errorMethod = findMethod(endpointClass, "onWebSocketError", Throwable.class); if (errorMethod != null) { MethodHandle errorHandle = toMethodHandle(lookup, errorMethod); metadata.setErrorHandle(errorHandle, errorMethod); } Method closeMethod = findMethod(endpointClass, "onWebSocketClose", int.class, String.class); if (closeMethod != null) { MethodHandle closeHandle = toMethodHandle(lookup, closeMethod); metadata.setCloseHandle(closeHandle, closeMethod); } return metadata; } private Method findMethod(Class klass, String name, Class... parameters) { // Verify if the method is overridden in the endpoint class, to avoid // calling all methods of Session.Listener even if they are not overridden. Method method = ReflectUtils.findMethod(klass, name, parameters); if (method == null) return null; if (isOverridden(method)) return method; return null; } private boolean isOverridden(Method method) { return method.getDeclaringClass() != Session.Listener.class; } private JettyWebSocketFrameHandlerMetadata createAnnotatedMetadata(WebSocket anno, Class endpointClass) { JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata(); metadata.setAutoDemand(anno.autoDemand()); MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass); // OnWebSocketOpen [0..1] Method onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketOpen.class); if (onmethod != null) { assertSignatureValid(endpointClass, onmethod, OnWebSocketOpen.class); final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class).required(); MethodHandle methodHandle = InvokerUtils.mutatedInvoker(lookup, endpointClass, onmethod, SESSION); metadata.setOpenHandle(methodHandle, onmethod); } // OnWebSocketClose [0..1] onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketClose.class); if (onmethod != null) { assertSignatureValid(endpointClass, onmethod, OnWebSocketClose.class); final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class); final InvokerUtils.Arg STATUS_CODE = new InvokerUtils.Arg(int.class); final InvokerUtils.Arg REASON = new InvokerUtils.Arg(String.class); MethodHandle methodHandle = InvokerUtils.mutatedInvoker(lookup, endpointClass, onmethod, SESSION, STATUS_CODE, REASON); metadata.setCloseHandle(methodHandle, onmethod); } // OnWebSocketError [0..1] onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketError.class); if (onmethod != null) { assertSignatureValid(endpointClass, onmethod, OnWebSocketError.class); final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class); final InvokerUtils.Arg CAUSE = new InvokerUtils.Arg(Throwable.class).required(); MethodHandle methodHandle = InvokerUtils.mutatedInvoker(lookup, endpointClass, onmethod, SESSION, CAUSE); metadata.setErrorHandle(methodHandle, onmethod); } // OnWebSocketFrame [0..1] onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketFrame.class); if (onmethod != null) { assertSignatureValid(endpointClass, onmethod, OnWebSocketFrame.class); final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class); final InvokerUtils.Arg FRAME = new InvokerUtils.Arg(Frame.class).required(); final InvokerUtils.Arg CALLBACK = new InvokerUtils.Arg(Callback.class).required(); MethodHandle methodHandle = InvokerUtils.mutatedInvoker(lookup, endpointClass, onmethod, SESSION, FRAME, CALLBACK); metadata.setFrameHandle(methodHandle, onmethod); } // OnWebSocketMessage [0..2] Method[] onMessages = ReflectUtils.findAnnotatedMethods(endpointClass, OnWebSocketMessage.class); if (onMessages != null) { // The different kind of @OnWebSocketMessage method parameter signatures expected for (Method onMsg : onMessages) { assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); MethodHandle methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, textCallingArgs); if (methodHandle != null) { // Normal Text Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setTextHandle(StringMessageSink.class, methodHandle, onMsg); continue; } methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, binaryBufferCallingArgs); if (methodHandle != null) { // ByteBuffer Binary Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setBinaryHandle(ByteBufferMessageSink.class, methodHandle, onMsg); continue; } methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, inputStreamCallingArgs); if (methodHandle != null) { if (!metadata.isAutoDemand()) throw new InvalidWebSocketException("InputStream methods require auto-demanding WebSocket endpoints"); // InputStream Binary Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setBinaryHandle(InputStreamMessageSink.class, methodHandle, onMsg); continue; } methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, readerCallingArgs); if (methodHandle != null) { if (!metadata.isAutoDemand()) throw new InvalidWebSocketException("Reader methods require auto-demanding WebSocket endpoints"); // Reader Text Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setTextHandle(ReaderMessageSink.class, methodHandle, onMsg); continue; } methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, textPartialCallingArgs); if (methodHandle != null) { // Partial Text Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setTextHandle(PartialStringMessageSink.class, methodHandle, onMsg); continue; } methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, binaryPartialBufferCallingArgs); if (methodHandle != null) { // Partial ByteBuffer Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setBinaryHandle(PartialByteBufferMessageSink.class, methodHandle, onMsg); continue; } // Not a valid @OnWebSocketMessage declaration signature throw InvalidSignatureException.build(endpointClass, OnWebSocketMessage.class, onMsg); } } return metadata; } private void assertSignatureValid(Class endpointClass, Method method, Class annotationClass) { // Test modifiers int mods = method.getModifiers(); if (!Modifier.isPublic(mods)) { StringBuilder err = new StringBuilder(); err.append("@").append(annotationClass.getSimpleName()); err.append(" method must be public: "); ReflectUtils.append(err, endpointClass, method); throw new InvalidSignatureException(err.toString()); } if (Modifier.isStatic(mods)) { StringBuilder err = new StringBuilder(); err.append("@").append(annotationClass.getSimpleName()); err.append(" method must not be static: "); ReflectUtils.append(err, endpointClass, method); throw new InvalidSignatureException(err.toString()); } // Test return type Class returnType = method.getReturnType(); if ((returnType == Void.TYPE) || (returnType == Void.class)) { // Void is 100% valid, always return; } StringBuilder err = new StringBuilder(); err.append("@").append(annotationClass.getSimpleName()); err.append(" return must be void: "); ReflectUtils.append(err, endpointClass, method); throw new InvalidSignatureException(err.toString()); } /** *

* Gives a {@link MethodHandles.Lookup} instance to be used to find methods in server classes. * For lookups on application classes use {@link #getApplicationMethodHandleLookup(Class)} instead. *

*

* This uses the caller sensitive {@link MethodHandles#lookup()}, this will allow MethodHandle access * to server classes we need to use and will give access permissions to private methods as well. *

* * @return a lookup object to be used to find methods on server classes. */ public static MethodHandles.Lookup getServerMethodHandleLookup() { return MethodHandles.lookup(); } /** *

* Gives a {@link MethodHandles.Lookup} instance to be used to find public methods in application classes. * For lookups on server classes use {@link #getServerMethodHandleLookup()} instead. *

*

* This uses {@link MethodHandles#publicLookup()} as we only need access to public method of the lookupClass. * To look up a method on the lookupClass, it must be public and the class must be accessible from this * module, so if the lookupClass is in a JPMS module it must be exported so that the public methods * of the lookupClass are accessible outside of the module. *

*

* The {@link java.lang.invoke.MethodHandles.Lookup#in(Class)} allows us to search specifically * in the endpoint Class to avoid any potential linkage errors which could occur if the same * class is present in multiple web apps. Unlike using {@link MethodHandles#publicLookup()} * using {@link MethodHandles#lookup()} with {@link java.lang.invoke.MethodHandles.Lookup#in(Class)} * will cause the lookup to lose its public access to the lookup class if they are in different modules. *

*

* {@link MethodHandles#privateLookupIn(Class, MethodHandles.Lookup)} is also unsuitable because it * requires the caller module to read the target module, and the target module to open reflective * access to the lookupClasses private methods. This is possible but requires extra configuration * to provide private access which is not necessary for the purpose of accessing the public methods. *

* * @param lookupClass the desired lookup class for the new lookup object. * @return a lookup object to be used to find methods on the lookupClass. */ public static MethodHandles.Lookup getApplicationMethodHandleLookup(Class lookupClass) { return MethodHandles.publicLookup().in(lookupClass); } @Override public void dump(Appendable out, String indent) throws IOException { dumpObjects(out, indent, metadataMap); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy