org.apache.tomcat.websocket.pojo.PojoMethodMapping Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.tomcat.websocket.pojo;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.websocket.CloseReason;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.DeploymentException;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.PongMessage;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.websocket.DecoderEntry;
import org.apache.tomcat.websocket.Util;
import org.apache.tomcat.websocket.Util.DecoderMatch;
/**
* For a POJO class annotated with
* {@link javax.websocket.server.ServerEndpoint}, an instance of this class
* creates and caches the method handler, method information and parameter
* information for the onXXX calls.
*/
public class PojoMethodMapping {
private static final StringManager sm =
StringManager.getManager(PojoMethodMapping.class);
private final Method onOpen;
private final Method onClose;
private final Method onError;
private final PojoPathParam[] onOpenParams;
private final PojoPathParam[] onCloseParams;
private final PojoPathParam[] onErrorParams;
private final List onMessage = new ArrayList();
private final String wsPath;
public PojoMethodMapping(Class> clazzPojo,
List> decoderClazzes, String wsPath)
throws DeploymentException {
this.wsPath = wsPath;
List decoders = Util.getDecoders(decoderClazzes);
Method open = null;
Method close = null;
Method error = null;
Method[] clazzPojoMethods = null;
Class> currentClazz = clazzPojo;
while (!currentClazz.equals(Object.class)) {
Method[] currentClazzMethods = currentClazz.getDeclaredMethods();
if (currentClazz == clazzPojo) {
clazzPojoMethods = currentClazzMethods;
}
for (Method method : currentClazzMethods) {
if (method.isSynthetic()) {
// Skip all synthetic methods.
// They may have copies of annotations from methods we are
// interested in and they will use the wrong parameter type
// (they always use Object) so we can't used them here.
continue;
}
if (method.getAnnotation(OnOpen.class) != null) {
checkPublic(method);
if (open == null) {
open = method;
} else {
if (currentClazz == clazzPojo ||
!isMethodOverride(open, method)) {
// Duplicate annotation
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateAnnotation",
OnOpen.class, currentClazz));
}
}
} else if (method.getAnnotation(OnClose.class) != null) {
checkPublic(method);
if (close == null) {
close = method;
} else {
if (currentClazz == clazzPojo ||
!isMethodOverride(close, method)) {
// Duplicate annotation
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateAnnotation",
OnClose.class, currentClazz));
}
}
} else if (method.getAnnotation(OnError.class) != null) {
checkPublic(method);
if (error == null) {
error = method;
} else {
if (currentClazz == clazzPojo ||
!isMethodOverride(error, method)) {
// Duplicate annotation
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateAnnotation",
OnError.class, currentClazz));
}
}
} else if (method.getAnnotation(OnMessage.class) != null) {
checkPublic(method);
MessageHandlerInfo messageHandler = new MessageHandlerInfo(method, decoders);
boolean found = false;
for (MessageHandlerInfo otherMessageHandler : onMessage) {
if (messageHandler.targetsSameWebSocketMessageType(otherMessageHandler)) {
found = true;
if (currentClazz == clazzPojo ||
!isMethodOverride(messageHandler.m, otherMessageHandler.m)) {
// Duplicate annotation
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateAnnotation",
OnMessage.class, currentClazz));
}
}
}
if (!found) {
onMessage.add(messageHandler);
}
} else {
// Method not annotated
}
}
currentClazz = currentClazz.getSuperclass();
}
// If the methods are not on clazzPojo and they are overridden
// by a non annotated method in clazzPojo, they should be ignored
if (open != null && open.getDeclaringClass() != clazzPojo) {
if (isOverridenWithoutAnnotation(clazzPojoMethods, open, OnOpen.class)) {
open = null;
}
}
if (close != null && close.getDeclaringClass() != clazzPojo) {
if (isOverridenWithoutAnnotation(clazzPojoMethods, close, OnClose.class)) {
close = null;
}
}
if (error != null && error.getDeclaringClass() != clazzPojo) {
if (isOverridenWithoutAnnotation(clazzPojoMethods, error, OnError.class)) {
error = null;
}
}
List overriddenOnMessage = new ArrayList();
for (MessageHandlerInfo messageHandler : onMessage) {
if (messageHandler.m.getDeclaringClass() != clazzPojo
&& isOverridenWithoutAnnotation(clazzPojoMethods, messageHandler.m, OnMessage.class)) {
overriddenOnMessage.add(messageHandler);
}
}
for (MessageHandlerInfo messageHandler : overriddenOnMessage) {
onMessage.remove(messageHandler);
}
this.onOpen = open;
this.onClose = close;
this.onError = error;
onOpenParams = getPathParams(onOpen, MethodType.ON_OPEN);
onCloseParams = getPathParams(onClose, MethodType.ON_CLOSE);
onErrorParams = getPathParams(onError, MethodType.ON_ERROR);
}
private void checkPublic(Method m) throws DeploymentException {
if (!Modifier.isPublic(m.getModifiers())) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.methodNotPublic", m.getName()));
}
}
private boolean isMethodOverride(Method method1, Method method2) {
return method1.getName().equals(method2.getName())
&& method1.getReturnType().equals(method2.getReturnType())
&& Arrays.equals(method1.getParameterTypes(), method2.getParameterTypes());
}
private boolean isOverridenWithoutAnnotation(Method[] methods,
Method superclazzMethod, Class extends Annotation> annotation) {
for (Method method : methods) {
if (isMethodOverride(method, superclazzMethod)
&& (method.getAnnotation(annotation) == null)) {
return true;
}
}
return false;
}
public String getWsPath() {
return wsPath;
}
public Method getOnOpen() {
return onOpen;
}
public Object[] getOnOpenArgs(Map pathParameters,
Session session, EndpointConfig config) throws DecodeException {
return buildArgs(onOpenParams, pathParameters, session, config, null,
null);
}
public Method getOnClose() {
return onClose;
}
public Object[] getOnCloseArgs(Map pathParameters,
Session session, CloseReason closeReason) throws DecodeException {
return buildArgs(onCloseParams, pathParameters, session, null, null,
closeReason);
}
public Method getOnError() {
return onError;
}
public Object[] getOnErrorArgs(Map pathParameters,
Session session, Throwable throwable) throws DecodeException {
return buildArgs(onErrorParams, pathParameters, session, null,
throwable, null);
}
public boolean hasMessageHandlers() {
return !onMessage.isEmpty();
}
public Set getMessageHandlers(Object pojo,
Map pathParameters, Session session,
EndpointConfig config) {
Set result = new HashSet();
for (MessageHandlerInfo messageMethod : onMessage) {
result.addAll(messageMethod.getMessageHandlers(pojo, pathParameters,
session, config));
}
return result;
}
private static PojoPathParam[] getPathParams(Method m,
MethodType methodType) throws DeploymentException {
if (m == null) {
return new PojoPathParam[0];
}
boolean foundThrowable = false;
Class>[] types = m.getParameterTypes();
Annotation[][] paramsAnnotations = m.getParameterAnnotations();
PojoPathParam[] result = new PojoPathParam[types.length];
for (int i = 0; i < types.length; i++) {
Class> type = types[i];
if (type.equals(Session.class)) {
result[i] = new PojoPathParam(type, null);
} else if (methodType == MethodType.ON_OPEN &&
type.equals(EndpointConfig.class)) {
result[i] = new PojoPathParam(type, null);
} else if (methodType == MethodType.ON_ERROR
&& type.equals(Throwable.class)) {
foundThrowable = true;
result[i] = new PojoPathParam(type, null);
} else if (methodType == MethodType.ON_CLOSE &&
type.equals(CloseReason.class)) {
result[i] = new PojoPathParam(type, null);
} else {
Annotation[] paramAnnotations = paramsAnnotations[i];
for (Annotation paramAnnotation : paramAnnotations) {
if (paramAnnotation.annotationType().equals(
PathParam.class)) {
result[i] = new PojoPathParam(type,
((PathParam) paramAnnotation).value());
break;
}
}
// Parameters without annotations are not permitted
if (result[i] == null) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.paramWithoutAnnotation",
type, m.getName(), m.getClass().getName()));
}
}
}
if (methodType == MethodType.ON_ERROR && !foundThrowable) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.onErrorNoThrowable",
m.getName(), m.getDeclaringClass().getName()));
}
return result;
}
private static Object[] buildArgs(PojoPathParam[] pathParams,
Map pathParameters, Session session,
EndpointConfig config, Throwable throwable, CloseReason closeReason)
throws DecodeException {
Object[] result = new Object[pathParams.length];
for (int i = 0; i < pathParams.length; i++) {
Class> type = pathParams[i].getType();
if (type.equals(Session.class)) {
result[i] = session;
} else if (type.equals(EndpointConfig.class)) {
result[i] = config;
} else if (type.equals(Throwable.class)) {
result[i] = throwable;
} else if (type.equals(CloseReason.class)) {
result[i] = closeReason;
} else {
String name = pathParams[i].getName();
String value = pathParameters.get(name);
try {
result[i] = Util.coerceToType(type, value);
} catch (Exception e) {
throw new DecodeException(value, sm.getString(
"pojoMethodMapping.decodePathParamFail",
value, type), e);
}
}
}
return result;
}
private static class MessageHandlerInfo {
private final Method m;
private int indexString = -1;
private int indexByteArray = -1;
private int indexByteBuffer = -1;
private int indexPong = -1;
private int indexBoolean = -1;
private int indexSession = -1;
private int indexInputStream = -1;
private int indexReader = -1;
private int indexPrimitive = -1;
private Map indexPathParams = new HashMap();
private int indexPayload = -1;
private DecoderMatch decoderMatch = null;
private long maxMessageSize = -1;
public MessageHandlerInfo(Method m, List decoderEntries)
throws DeploymentException {
this.m = m;
Class>[] types = m.getParameterTypes();
Annotation[][] paramsAnnotations = m.getParameterAnnotations();
for (int i = 0; i < types.length; i++) {
boolean paramFound = false;
Annotation[] paramAnnotations = paramsAnnotations[i];
for (Annotation paramAnnotation : paramAnnotations) {
if (paramAnnotation.annotationType().equals(
PathParam.class)) {
indexPathParams.put(
Integer.valueOf(i), new PojoPathParam(types[i],
((PathParam) paramAnnotation).value()));
paramFound = true;
break;
}
}
if (paramFound) {
continue;
}
if (String.class.isAssignableFrom(types[i])) {
if (indexString == -1) {
indexString = i;
} else {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
}
} else if (Reader.class.isAssignableFrom(types[i])) {
if (indexReader == -1) {
indexReader = i;
} else {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
}
} else if (boolean.class == types[i]) {
if (indexBoolean == -1) {
indexBoolean = i;
} else {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateLastParam",
m.getName(), m.getDeclaringClass().getName()));
}
} else if (ByteBuffer.class.isAssignableFrom(types[i])) {
if (indexByteBuffer == -1) {
indexByteBuffer = i;
} else {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
}
} else if (byte[].class == types[i]) {
if (indexByteArray == -1) {
indexByteArray = i;
} else {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
}
} else if (InputStream.class.isAssignableFrom(types[i])) {
if (indexInputStream == -1) {
indexInputStream = i;
} else {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
}
} else if (Util.isPrimitive(types[i])) {
if (indexPrimitive == -1) {
indexPrimitive = i;
} else {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
}
} else if (Session.class.isAssignableFrom(types[i])) {
if (indexSession == -1) {
indexSession = i;
} else {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateSessionParam",
m.getName(), m.getDeclaringClass().getName()));
}
} else if (PongMessage.class.isAssignableFrom(types[i])) {
if (indexPong == -1) {
indexPong = i;
} else {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicatePongMessageParam",
m.getName(), m.getDeclaringClass().getName()));
}
} else {
if (decoderMatch != null && decoderMatch.hasMatches()) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
}
decoderMatch = new DecoderMatch(types[i], decoderEntries);
if (decoderMatch.hasMatches()) {
indexPayload = i;
} else {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.noDecoder",
m.getName(), m.getDeclaringClass().getName()));
}
}
}
// Additional checks required
if (indexString != -1) {
if (indexPayload != -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
} else {
indexPayload = indexString;
}
}
if (indexReader != -1) {
if (indexPayload != -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
} else {
indexPayload = indexReader;
}
}
if (indexByteArray != -1) {
if (indexPayload != -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
} else {
indexPayload = indexByteArray;
}
}
if (indexByteBuffer != -1) {
if (indexPayload != -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
} else {
indexPayload = indexByteBuffer;
}
}
if (indexInputStream != -1) {
if (indexPayload != -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
} else {
indexPayload = indexInputStream;
}
}
if (indexPrimitive != -1) {
if (indexPayload != -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateMessageParam",
m.getName(), m.getDeclaringClass().getName()));
} else {
indexPayload = indexPrimitive;
}
}
if (indexPong != -1) {
if (indexPayload != -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.pongWithPayload",
m.getName(), m.getDeclaringClass().getName()));
} else {
indexPayload = indexPong;
}
}
if (indexPayload == -1 && indexPrimitive == -1 &&
indexBoolean != -1) {
// The boolean we found is a payload, not a last flag
indexPayload = indexBoolean;
indexPrimitive = indexBoolean;
indexBoolean = -1;
}
if (indexPayload == -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.noPayload",
m.getName(), m.getDeclaringClass().getName()));
}
if (indexPong != -1 && indexBoolean != -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.partialPong",
m.getName(), m.getDeclaringClass().getName()));
}
if(indexReader != -1 && indexBoolean != -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.partialReader",
m.getName(), m.getDeclaringClass().getName()));
}
if(indexInputStream != -1 && indexBoolean != -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.partialInputStream",
m.getName(), m.getDeclaringClass().getName()));
}
if (decoderMatch != null && decoderMatch.hasMatches() &&
indexBoolean != -1) {
throw new DeploymentException(sm.getString(
"pojoMethodMapping.partialObject",
m.getName(), m.getDeclaringClass().getName()));
}
maxMessageSize = m.getAnnotation(OnMessage.class).maxMessageSize();
}
public boolean targetsSameWebSocketMessageType(MessageHandlerInfo otherHandler) {
if (otherHandler == null) {
return false;
}
return isPong() && otherHandler.isPong() || isBinary() && otherHandler.isBinary() ||
isText() && otherHandler.isText();
}
private boolean isPong() {
return indexPong >= 0;
}
private boolean isText() {
return indexString >= 0 || indexPrimitive >= 0 || indexReader >= 0 ||
(decoderMatch != null && decoderMatch.getTextDecoders().size() > 0);
}
private boolean isBinary() {
return indexByteArray >= 0 || indexByteBuffer >= 0 || indexInputStream >= 0 ||
(decoderMatch != null && decoderMatch.getBinaryDecoders().size() > 0);
}
public Set getMessageHandlers(Object pojo,
Map pathParameters, Session session,
EndpointConfig config) {
Object[] params = new Object[m.getParameterTypes().length];
for (Map.Entry entry :
indexPathParams.entrySet()) {
PojoPathParam pathParam = entry.getValue();
String valueString = pathParameters.get(pathParam.getName());
Object value = null;
try {
value = Util.coerceToType(pathParam.getType(), valueString);
} catch (Exception e) {
DecodeException de = new DecodeException(valueString,
sm.getString(
"pojoMethodMapping.decodePathParamFail",
valueString, pathParam.getType()), e);
params = new Object[] { de };
break;
}
params[entry.getKey().intValue()] = value;
}
Set results = new HashSet(2);
if (indexBoolean == -1) {
// Basic
if (indexString != -1 || indexPrimitive != -1) {
MessageHandler mh = new PojoMessageHandlerWholeText(pojo, m,
session, config, null, params, indexPayload, false,
indexSession, maxMessageSize);
results.add(mh);
} else if (indexReader != -1) {
MessageHandler mh = new PojoMessageHandlerWholeText(pojo, m,
session, config, null, params, indexReader, true,
indexSession, maxMessageSize);
results.add(mh);
} else if (indexByteArray != -1) {
MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
m, session, config, null, params, indexByteArray,
true, indexSession, false, maxMessageSize);
results.add(mh);
} else if (indexByteBuffer != -1) {
MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
m, session, config, null, params, indexByteBuffer,
false, indexSession, false, maxMessageSize);
results.add(mh);
} else if (indexInputStream != -1) {
MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
m, session, config, null, params, indexInputStream,
true, indexSession, true, maxMessageSize);
results.add(mh);
} else if (decoderMatch != null && decoderMatch.hasMatches()) {
if (decoderMatch.getBinaryDecoders().size() > 0) {
MessageHandler mh = new PojoMessageHandlerWholeBinary(
pojo, m, session, config,
decoderMatch.getBinaryDecoders(), params,
indexPayload, true, indexSession, true,
maxMessageSize);
results.add(mh);
}
if (decoderMatch.getTextDecoders().size() > 0) {
MessageHandler mh = new PojoMessageHandlerWholeText(
pojo, m, session, config,
decoderMatch.getTextDecoders(), params,
indexPayload, true, indexSession, maxMessageSize);
results.add(mh);
}
} else {
MessageHandler mh = new PojoMessageHandlerWholePong(pojo, m,
session, params, indexPong, false, indexSession);
results.add(mh);
}
} else {
// ASync
if (indexString != -1) {
MessageHandler mh = new PojoMessageHandlerPartialText(pojo,
m, session, params, indexString, false,
indexBoolean, indexSession, maxMessageSize);
results.add(mh);
} else if (indexByteArray != -1) {
MessageHandler mh = new PojoMessageHandlerPartialBinary(
pojo, m, session, params, indexByteArray, true,
indexBoolean, indexSession, maxMessageSize);
results.add(mh);
} else {
MessageHandler mh = new PojoMessageHandlerPartialBinary(
pojo, m, session, params, indexByteBuffer, false,
indexBoolean, indexSession, maxMessageSize);
results.add(mh);
}
}
return results;
}
}
private enum MethodType {
ON_OPEN,
ON_CLOSE,
ON_ERROR
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy