advantageous/qbit

Bug with Reakt Promise and WebSocket call

Closed this issue · 0 comments

Bug with Reakt Promise and WebSocket call.
This bug was masked when testing serviceBundle and websocket proxy in the same unit test.
Now we pulled this out so it is just a WebSocket proxy test.

BoonClient (used for websocket RPC proxy creation) was not handling the Promise case.

    /**
     * Create an async handler. Uses some generics reflection to see what the actual type is
     *
     * @param serviceInterface client interface
     * @param call             method call object
     * @param handler          handler that will handle the message
     * @param <T>              the class of hte client interface
     * @return the new handler
     */
    private <T> Callback createHandler(final Class<T> serviceInterface, final MethodCall call,
                                       final Callback handler) {

        final ClassMeta<T> clsMeta = ClassMeta.classMeta(serviceInterface);
        final MethodAccess method = clsMeta.method(call.name());

        Class<?> returnType = null;
        Class<?> compType = null;

        if (method.returnType() == Promise.class) {

            Type t0 = method.method().getGenericReturnType();
            if (t0 instanceof ParameterizedType) {
                ParameterizedType parameterizedType =((ParameterizedType) t0);
                Type type = ((parameterizedType != null ? parameterizedType.getActualTypeArguments().length : 0) > 0 ? (parameterizedType != null ? parameterizedType.getActualTypeArguments() : new Type[0])[0] : null);

                if (type instanceof ParameterizedType) {
                    returnType = (Class) ((ParameterizedType) type).getRawType();
                    final Type type1 = ((ParameterizedType) type).getActualTypeArguments()[0];

                    if (type1 instanceof Class) {
                        compType = (Class) type1;
                    }
                } else if (type instanceof Class) {
                    returnType = (Class<?>) type;
                }

            }
        } else {
            if (method.parameterTypes().length > 0) {
                Type[] genericParameterTypes = method.getGenericParameterTypes();
                ParameterizedType parameterizedType = genericParameterTypes.length > 0 ? (ParameterizedType) genericParameterTypes[0] : null;

                Type type = ((parameterizedType != null ? parameterizedType.getActualTypeArguments().length : 0) > 0 ? (parameterizedType != null ? parameterizedType.getActualTypeArguments() : new Type[0])[0] : null);

                if (type instanceof ParameterizedType) {
                    returnType = (Class) ((ParameterizedType) type).getRawType();
                    final Type type1 = ((ParameterizedType) type).getActualTypeArguments()[0];

                    if (type1 instanceof Class) {
                        compType = (Class) type1;
                    }
                } else if (type instanceof Class) {
                    returnType = (Class<?>) type;
                }

            }
        }
        final Class<?> actualReturnType = returnType;

        final Class<?> componentClass = compType;

        /** Create the return handler. */
        return new Callback<Object>() {
            @Override
            public void accept(Object event) {

                if (actualReturnType != null) {

                    if (componentClass != null && actualReturnType == List.class) {

                        try {
                            //noinspection unchecked
                            event = MapObjectConversion.convertListOfMapsToObjects(componentClass, (List) event);
                        } catch (Exception ex) {
                            if (event instanceof CharSequence) {
                                String errorMessage = event.toString();
                                if (errorMessage.startsWith("java.lang.IllegalState")) {
                                    handler.onError(new IllegalStateException(errorMessage));
                                    return;
                                } else {
                                    handler.onError(new IllegalStateException("Conversion error"));
                                    return;
                                }
                            } else {
                                handler.onError(new IllegalStateException("Conversion error"));
                                return;
                            }
                        }
                    } else {

                        event = Conversions.coerce(actualReturnType, event);
                    }
                    //noinspection unchecked
                    handler.accept(event);
                }


            }

            @Override
            public void onError(Throwable error) {
                handler.onError(error);
            }

            @Override
            public void onTimeout() {
                handler.onTimeout();
            }
        };


    }

A few things I noticed about BoonClient while I was in there.
It always expects the callback to be the first argument.
(Callback probably should be the last but we have to maintain backwards compatibility, but we should fix this or document it.)

Now it handles promise returns fine.

The second thing that I noticed is that we only handle List/Collections not maps which is probably ok, but again should be changed or documented.

The third thing I noticed was that it uses ParameterizedType which works fine for Java and Scala but does not handle WildCar type which if I remember correctly Kotlin needs.