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

org.primefaces.push.impl.PushEndpointHandlerProxy Maven / Gradle / Ivy

There is a newer version: 14.0.0-RC2
Show newest version
/*
 * Copyright 2009-2014 PrimeTek.
 *
 * Licensed 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.primefaces.push.impl;

import org.atmosphere.config.managed.Decoder;
import org.atmosphere.config.managed.Encoder;
import org.atmosphere.config.managed.Invoker;
import org.atmosphere.config.managed.ManagedServiceInterceptor;
import org.atmosphere.cpr.ApplicationConfig;
import org.atmosphere.cpr.AtmosphereConfig;
import org.atmosphere.cpr.AtmosphereRequest;
import org.atmosphere.cpr.AtmosphereResource;
import org.atmosphere.cpr.AtmosphereResourceEvent;
import org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter;
import org.atmosphere.cpr.AtmosphereResourceImpl;
import org.atmosphere.cpr.BroadcastFilter;
import org.atmosphere.cpr.PerRequestBroadcastFilter;
import org.atmosphere.handler.AbstractReflectorAtmosphereHandler;
import org.atmosphere.handler.AnnotatedProxy;
import org.atmosphere.util.IOUtils;
import org.primefaces.push.EventBus;
import org.primefaces.push.RemoteEndpoint;
import org.primefaces.push.Status;
import org.primefaces.push.annotation.OnClose;
import org.primefaces.push.annotation.OnMessage;
import org.primefaces.push.annotation.OnOpen;
import org.primefaces.push.annotation.PathParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

public class PushEndpointHandlerProxy extends AbstractReflectorAtmosphereHandler implements AnnotatedProxy{

    private Logger logger = LoggerFactory.getLogger(PushEndpointHandlerProxy.class);
    private Object proxiedInstance;
    private List onMessageMethods;
    private Method onCloseMethod;
    private Method onTimeoutMethod;
    private Method onOpenMethod;
    private Method onResumeMethod;
    private AtmosphereConfig config;
    private EventBus eventBus;
    private boolean injectEventBus = false;
    private boolean injectEndpoint = false;
    private boolean pathParams;

    final Map>> encoders = new HashMap>>();
    final Map>> decoders = new HashMap>>();

    private final Set trackedUUID = Collections.synchronizedSet(new HashSet());


    // Duplicate message because of Atmosphere 2.2.x API Changes from 2.1.x
    private final BroadcastFilter onMessageFilter = new BroadcastFilter() {
        //@Override
        public BroadcastAction filter(Object originalMessage, Object message) {
            return invoke(null, originalMessage, message);

        }

        public BroadcastAction filter(String broadccasterId, Object originalMessage, Object message) {
            return filter(originalMessage, message);
        }
    };

    // Duplicate message because of Atmosphere 2.2.x API Changes from 2.1.x
    private final BroadcastFilter onPerMessageFilter = new PerRequestBroadcastFilter() {

        //@Override
        public BroadcastAction filter(Object originalMessage, Object message) {
            return invoke(null, originalMessage, message);
        }

        //@Override
        public BroadcastAction filter(AtmosphereResource r, Object originalMessage, Object message) {
            RemoteEndpointImpl rm = (RemoteEndpointImpl) r.getRequest().getAttribute(RemoteEndpointImpl.class.getName());
            return invoke(rm, originalMessage, message);
        }

        //@Override
        public BroadcastAction filter(String broadccasterId, Object originalMessage, Object message) {
            return filter(originalMessage, message);
        }

        //@Override
        public BroadcastAction filter(String broadccasterId, AtmosphereResource r, Object originalMessage, Object message) {
            return filter(r, originalMessage, message);
        }
    };

    private BroadcastFilter.BroadcastAction invoke(RemoteEndpoint r, Object originalMessage, Object message) {
        Object o;
        o = message(r, message);
        if (o != null) {
            return new BroadcastFilter.BroadcastAction(BroadcastFilter.BroadcastAction.ACTION.CONTINUE, o);
        }
        return new BroadcastFilter.BroadcastAction(BroadcastFilter.BroadcastAction.ACTION.CONTINUE, message);
    }

    public PushEndpointHandlerProxy() {
    }

    public AnnotatedProxy configure(AtmosphereConfig config, Object c) {
        this.proxiedInstance = c;
        this.onMessageMethods = populateMessage(c, OnMessage.class);
        this.onCloseMethod = populate(c, OnClose.class);
        this.onTimeoutMethod = populate(c, OnClose.class);
        this.onOpenMethod = populate(c, OnOpen.class);
        this.onResumeMethod = populate(c, OnClose.class);
        this.config = config;
        this.eventBus = (EventBus) config.properties().get("evenBus");
        this.pathParams = pathParams(c);

        if (onMessageMethods.size() > 0) {
            populateEncoders();
            populateDecoders();
        }
        return this;
    }

    //@Override
    public void onRequest(final AtmosphereResource resource) throws IOException {
        final AtmosphereRequest request = resource.getRequest();
        Object b = IOUtils.readEntirely(resource).toString();
        String body = b.toString();

        final RemoteEndpointImpl remoteEndpoint = new RemoteEndpointImpl(request, body);
        String method = request.getMethod();

        resource.getBroadcaster().getBroadcasterConfig().addFilter(injectEndpoint ? onPerMessageFilter : onMessageFilter);

        request.setAttribute(RemoteEndpointImpl.class.getName(), remoteEndpoint);

        if (onOpenMethod != null) {
            resource.addEventListener(new AtmosphereResourceEventListenerAdapter() {
                @Override
                public void onSuspend(AtmosphereResourceEvent event) {
                    try {
                        if (!trackedUUID.add(resource.uuid())) return;

                        // TODO: Document this behavior
                        // Temporary remove the resource from being the target for event, to avoid long-poling loop.
                        event.broadcaster().removeAtmosphereResource(resource);
                        try {
                            invokeOpenOrClose(onOpenMethod, remoteEndpoint);
                        } finally {
                            event.broadcaster().addAtmosphereResource(resource);
                        }
                    } finally {
                        event.getResource().removeEventListener(this);
                    }
                }
            });
        }

        if (onResumeMethod != null) {
            resource.addEventListener(new AtmosphereResourceEventListenerAdapter() {
                @Override
                public void onResume(AtmosphereResourceEvent event) {
                    if (event.isResumedOnTimeout()) {
                        try {
                            invokeOpenOrClose(onResumeMethod, remoteEndpoint);
                        } finally {
                            event.getResource().removeEventListener(this);
                        }
                    }
                }
            });
        }

        if (method.equalsIgnoreCase("post")) {
            resource.getRequest().body(body);

            if (!body.isEmpty()) {
                Object o = null;
                try {
                    o = invoke(remoteEndpoint, body);
                } catch (IOException e) {
                    logger.error("", e);
                }
                if (o != null) {
                    resource.getBroadcaster().broadcast(o);
                }
            } else {
                logger.warn("{} received an empty body", ManagedServiceInterceptor.class.getSimpleName());
            }
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void onStateChange(AtmosphereResourceEvent event) throws IOException {

        // Original Value
        AtmosphereResourceImpl r = AtmosphereResourceImpl.class.cast(event.getResource());
        AtmosphereRequest request = r.getRequest(false);
        Boolean resumeOnBroadcast = r.resumeOnBroadcast();
        if (!resumeOnBroadcast) {
            // For legacy reason, check the attribute as well
            Object o = request.getAttribute(ApplicationConfig.RESUME_ON_BROADCAST);
            if (o != null && Boolean.class.isAssignableFrom(o.getClass())) {
                resumeOnBroadcast = Boolean.class.cast(o);
            }
        }

        // Disable resume so cached message can be send in one chunk.
        if (resumeOnBroadcast) {
            r.resumeOnBroadcast(false);
            request.setAttribute(ApplicationConfig.RESUME_ON_BROADCAST, false);
        }

        RemoteEndpointImpl remoteEndpoint = (RemoteEndpointImpl) request.getAttribute(RemoteEndpointImpl.class.getName());

        if (event.isCancelled() || event.isClosedByClient()) {
            remoteEndpoint.status().status(event.isCancelled() ? Status.STATUS.UNEXPECTED_CLOSE : Status.STATUS.CLOSED_BY_CLIENT);
            request.removeAttribute(RemoteEndpointImpl.class.getName());
            trackedUUID.remove(r.uuid());

            invokeOpenOrClose(onCloseMethod, remoteEndpoint);
        } else if (event.isResumedOnTimeout() || event.isResuming()) {
            remoteEndpoint.status().status(Status.STATUS.CLOSED_BY_TIMEOUT);
            request.removeAttribute(RemoteEndpointImpl.class.getName());

            invokeOpenOrClose(onTimeoutMethod, remoteEndpoint);
        } else {
            super.onStateChange(event);
        }

        if (resumeOnBroadcast && r.isSuspended()) {
            r.resume();
        }
    }

    public boolean pathParams(){
        return pathParams;
    }

    protected boolean pathParams(Object o){
        for (Field field : o.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(PathParam.class)) {
                return true;
            }
        }
        return false;
    }

    public Object invoke(RemoteEndpointImpl resource, Object msg) throws IOException {
        return message(resource, msg);
    }

    private Method populate(Object c, Class annotation) {
        for (Method m : c.getClass().getMethods()) {
            if (m.isAnnotationPresent(annotation)) {
                return m;
            }
        }
        return null;
    }

    private List populateMessage(Object c, Class annotation) {
        ArrayList methods = new ArrayList();
        for (Method m : c.getClass().getMethods()) {
            if (m.isAnnotationPresent(annotation)) {

                Class[] cls = m.getParameterTypes();
                List> classes = Arrays.asList(cls);
                if (classes.contains(EventBus.class)) {
                    injectEventBus = true;
                } else if (classes.contains(RemoteEndpoint.class)) {
                    injectEndpoint = true;
                }
                methods.add(m);
            }
        }
        return methods;
    }

    private void populateEncoders() {
        for (Method m : onMessageMethods) {
            List> l = new CopyOnWriteArrayList>();
            for (Class s : m.getAnnotation(OnMessage.class).encoders()) {
                try {
                    l.add(config.framework().newClassInstance(Encoder.class, s));
                } catch (Exception e) {
                    logger.error("Unable to load encoder {}", s);
                }
            }
            encoders.put(m, l);
        }
    }

    private void populateDecoders() {
        for (Method m : onMessageMethods) {
            List> l = new CopyOnWriteArrayList>();
            for (Class s : m.getAnnotation(OnMessage.class).decoders()) {
                try {
                    l.add(config.framework().newClassInstance(Decoder.class, s));
                } catch (Exception e) {
                    logger.error("Unable to load encoder {}", s);
                }
            }
            decoders.put(m, l);
        }
    }

    private Object message(RemoteEndpoint resource, Object o) {
        try {
            // TODO: this code is hacky, but documentation will clarify what is supported.
            // TODO: Improve Injection, allow EventBus without RemoteEndpoint Injection.
            for (Method m : onMessageMethods) {
                Object decoded = Invoker.decode(decoders.get(m), o);
                if (decoded == null) {
                    decoded = o;
                }
                Object objectToEncode = null;
                if (m.getParameterTypes().length == 3 && resource != null) {
                    objectToEncode = Invoker.invokeMethod(m, proxiedInstance, resource, eventBus, decoded);
                } else if (m.getParameterTypes().length == 2 && resource != null) {
                    if (!injectEventBus) {
                        objectToEncode = Invoker.invokeMethod(m, proxiedInstance, resource, decoded);
                    } else if (objectToEncode == null) {
                        objectToEncode = Invoker.invokeMethod(m, proxiedInstance, eventBus, decoded);
                    }
                } else {
                    objectToEncode = Invoker.invokeMethod(m, proxiedInstance, decoded);
                }

                if (objectToEncode != null) {
                    return Invoker.encode(encoders.get(m), objectToEncode);
                }
            }
        } catch (Throwable t) {
            logger.error("", t);
        }
        return null;
    }

    //@Override
    public Object target() {
        return proxiedInstance;
    }

    protected void invokeOpenOrClose(Method m, RemoteEndpointImpl r) {
        Object objectToEncode = null;
        if (m.getParameterTypes().length == 2) {
            Invoker.invokeMethod(m, proxiedInstance, r, eventBus);
        } else if (!injectEventBus) {
            Invoker.invokeMethod(m, proxiedInstance, r);
        } else if (objectToEncode == null) {
            Invoker.invokeMethod(m, proxiedInstance, eventBus);
        }
    }

    @Override
    public String toString() {
        return "PushEndpointHandlerProxy proxy for " + proxiedInstance.getClass().getName();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy