org.red5.server.net.remoting.FlexMessagingService.java Source code

Java tutorial

Introduction

Here is the source code for org.red5.server.net.remoting.FlexMessagingService.java

Source

/*
 * RED5 Open Source Flash Server - https://github.com/Red5/
 * 
 * Copyright 2006-2015 by respective authors (see below). All rights reserved.
 * 
 * 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.red5.server.net.remoting;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.lang3.StringUtils;
import org.red5.compatibility.flex.data.messages.DataMessage;
import org.red5.compatibility.flex.data.messages.SequencedMessage;
import org.red5.compatibility.flex.messaging.messages.AbstractMessage;
import org.red5.compatibility.flex.messaging.messages.AcknowledgeMessage;
import org.red5.compatibility.flex.messaging.messages.AcknowledgeMessageExt;
import org.red5.compatibility.flex.messaging.messages.AsyncMessage;
import org.red5.compatibility.flex.messaging.messages.AsyncMessageExt;
import org.red5.compatibility.flex.messaging.messages.CommandMessage;
import org.red5.compatibility.flex.messaging.messages.CommandMessageExt;
import org.red5.compatibility.flex.messaging.messages.Constants;
import org.red5.compatibility.flex.messaging.messages.ErrorMessage;
import org.red5.compatibility.flex.messaging.messages.Message;
import org.red5.compatibility.flex.messaging.messages.RemotingMessage;
import org.red5.io.utils.ConversionUtils;
import org.red5.io.utils.RandomGUID;
import org.red5.server.api.IClient;
import org.red5.server.api.Red5;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IServiceInvoker;
import org.red5.server.exception.ClientDetailsException;
import org.red5.server.messaging.ServiceAdapter;
import org.red5.server.service.PendingCall;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Service that can execute compatibility Flex messages.
 * 
 * @author The Red5 Project
 * @author Joachim Bauch (jojo@struktur.de)
 * @author Paul Gregoire (mondain@gmail.com)
 */
public class FlexMessagingService {

    /** Name of the service. */
    public static final String SERVICE_NAME = "flexMessaging";

    /**
     * Logger
     */
    protected static Logger log = LoggerFactory.getLogger(FlexMessagingService.class);

    /** Service invoker to use. */
    protected IServiceInvoker serviceInvoker;

    /** Configured endpoints. */
    @SuppressWarnings("unchecked")
    protected Map<String, Object> endpoints = Collections.EMPTY_MAP;

    /** Registered clients. */
    protected ConcurrentMap<String, ServiceAdapter> registrations = null;

    /**
     * Setup available end points.
     * 
     * @param endpoints end points
     */
    public void setEndpoints(Map<String, Object> endpoints) {
        this.endpoints = endpoints;
        log.info("Configured endpoints: {}", endpoints);
    }

    /**
     * Set the service invoker to use.
     * 
     * @param serviceInvoker service invoker
     */
    public void setServiceInvoker(IServiceInvoker serviceInvoker) {
        this.serviceInvoker = serviceInvoker;
    }

    /**
     * Construct error message.
     * 
     * @param request request
     * @param faultCode fault code
     * @param faultString fault string
     * @param faultDetail fault detail
     * @return error message
     */
    public static ErrorMessage returnError(AbstractMessage request, String faultCode, String faultString,
            String faultDetail) {
        ErrorMessage result = new ErrorMessage();
        result.timestamp = System.currentTimeMillis();
        result.headers = request.headers;
        result.destination = request.destination;
        result.correlationId = request.messageId;
        result.faultCode = faultCode;
        result.faultString = faultString;
        result.faultDetail = faultDetail;
        return result;
    }

    /**
     * Construct error message from exception.
     * 
     * @param request request
     * @param faultCode fault code
     * @param faultString fautl string
     * @param error error
     * @return message
     */
    public static ErrorMessage returnError(AbstractMessage request, String faultCode, String faultString,
            Throwable error) {
        ErrorMessage result = returnError(request, faultCode, faultString, "");
        if (error instanceof ClientDetailsException) {
            result.extendedData = ((ClientDetailsException) error).getParameters();
            if (((ClientDetailsException) error).includeStacktrace()) {
                StringBuilder stack = new StringBuilder();
                for (StackTraceElement element : error.getStackTrace()) {
                    stack.append(element.toString()).append('\n');
                }
                result.faultDetail = stack.toString();
            }
        }
        result.rootCause = error;
        return result;
    }

    /**
     * Handle request coming from <pre>mx:RemoteObject</pre> tags.
     * 
     * @see <a href="http://livedocs.adobe.com/flex/2/langref/mx/rpc/remoting/mxml/RemoteObject.html">Adobe Livedocs (external)</a>
     *
     * @param msg message
     * @return aynsc message
     */
    public AsyncMessage handleRequest(RemotingMessage msg) {
        log.debug("Handle RemotingMessage request");
        log.trace("{}", msg);
        setClientId(msg);
        if (serviceInvoker == null) {
            log.error("No service invoker configured: {}", msg);
            return returnError(msg, "Server.Invoke.Error", "No service invoker configured.",
                    "No service invoker configured.");
        }

        Object endpoint = endpoints.get(msg.destination);
        log.debug("End point / destination: {}", endpoint);
        if (endpoint == null) {
            String errMsg = String.format("Endpoint %s doesn't exist.", msg.destination);
            log.debug("{} ({})", errMsg, msg);
            return returnError(msg, "Server.Invoke.Error", errMsg, errMsg);
        }

        //prepare an ack message
        AcknowledgeMessage result = new AcknowledgeMessage();
        result.setClientId(msg.getClientId());
        result.setCorrelationId(msg.getMessageId());

        //grab any headers
        Map<String, Object> headers = msg.getHeaders();
        log.debug("Headers: {}", headers);
        //if (headers.containsKey(Message.FLEX_CLIENT_ID_HEADER)) {
        //   headers.put(Message.FLEX_CLIENT_ID_HEADER, msg.getClientId());
        //}
        //result.setHeaders(headers);

        //get the operation
        String operation = msg.operation;
        log.debug("Operation: {}", operation);

        if (endpoint instanceof ServiceAdapter) {
            log.debug("Endpoint is a ServiceAdapter so message will be invoked");
            ServiceAdapter adapter = (ServiceAdapter) endpoint;
            //the result of the invocation will make up the message body
            result.body = adapter.invoke(msg);
        } else {
            //get arguments
            Object[] args = null;
            try {
                log.debug("Body: {} type: {}", msg.body, msg.body.getClass().getName());
                args = (Object[]) ConversionUtils.convert(msg.body, Object[].class);
            } catch (ConversionException cex) {
                //if the conversion fails and the endpoint is not a ServiceAdapter
                //just drop the object directly into an array
                args = new Object[] { msg.body };
            }

            IPendingServiceCall call = new PendingCall(operation, args);
            try {
                if (!serviceInvoker.invoke(call, endpoint)) {
                    if (call.getException() != null) {
                        // Use regular exception handling
                        Throwable err = call.getException();
                        return returnError(msg, "Server.Invoke.Error", err.getMessage(), err);
                    }
                    return returnError(msg, "Server.Invoke.Error", "Can't invoke method.", "");
                }
            } catch (Throwable err) {
                log.error("Error while invoking method.", err);
                return returnError(msg, "Server.Invoke.Error", err.getMessage(), err);
            }

            //we got a valid result from the method call.
            result.body = call.getResult();
        }

        return result;
    }

    /**
     * Handle command message (external) request.
     * 
     * @param msg message
     * @return message
     */
    public Message handleRequest(CommandMessageExt msg) {
        log.debug("Handle CommandMessageExt request");
        log.trace("{}", msg);
        setClientId(msg);
        String clientId = msg.getClientId();

        //process messages to non-service adapter end-points
        switch (msg.operation) {
        case Constants.POLL_OPERATION: //2
            //send back modifications
            log.debug("Poll: {}", clientId);
            //send back stored updates for this client
            if (registrations.containsKey(clientId)) {
                ServiceAdapter adapter = registrations.get(clientId);
                if (adapter != null) {
                    CommandMessage result = new CommandMessage();
                    result.setOperation(Constants.CLIENT_SYNC_OPERATION);
                    //result.setCorrelationId(msg.getMessageId());
                    //this will be the body of the responding command message
                    AsyncMessageExt ext = new AsyncMessageExt();
                    ext.setClientId(clientId);
                    ext.setCorrelationId(msg.getMessageId());
                    ext.setDestination("Red5Chat");
                    ext.setBody(adapter.manage(msg));
                    /*
                    //grab any headers
                    Map<String, Object> headers = msg.getHeaders();
                    log.debug("Headers: {}", headers);
                    if (headers.containsKey(Message.FLEX_CLIENT_ID_HEADER)) {
                       headers.put(Message.FLEX_CLIENT_ID_HEADER, msg.getClientId());
                    }
                    ext.setHeaders(headers);
                    */
                    //add as a child (body) of the command message
                    result.setBody(new Object[] { ext });

                    return result;
                } else {
                    log.warn("Adapter was not available");
                }
            }
            break;
        default:
            log.error("Unhandled CommandMessageExt request: {}", msg);
            String errMsg = String.format("Don't know how to handle %s", msg);
            return returnError(msg, "notImplemented", errMsg, errMsg);
        }

        AcknowledgeMessageExt result = new AcknowledgeMessageExt();
        result.setClientId(clientId);
        result.setCorrelationId(msg.getMessageId());

        return result;
    }

    /**
     * Handle command message request.
     * 
     * @param msg message
     * @return message
     */
    public Message handleRequest(CommandMessage msg) {
        log.debug("Handle CommandMessage request");
        log.trace("{}", msg);
        setClientId(msg);
        String clientId = msg.getClientId();

        //grab any headers
        Map<String, Object> headers = msg.getHeaders();
        log.debug("Headers: {}", headers);
        if (headers.containsKey(Message.FLEX_CLIENT_ID_HEADER)) {
            headers.put(Message.FLEX_CLIENT_ID_HEADER, msg.getClientId());
        }

        String destination = msg.getDestination();
        log.debug("Destination: {}", destination);

        //process messages to non-service adapter end-points
        switch (msg.operation) {
        case Constants.CLIENT_PING_OPERATION: //5
            //send back pong message
            break;

        case Constants.POLL_OPERATION: //2
            //send back modifications
            log.debug("Poll: {}", clientId);
            //send back stored updates for this client
            if (registrations.containsKey(clientId)) {
                ServiceAdapter adapter = registrations.get(clientId);
                if (adapter != null) {
                    CommandMessage result = new CommandMessage();
                    result.setOperation(Constants.CLIENT_SYNC_OPERATION);
                    //result.setCorrelationId(msg.getMessageId());
                    //this will be the body of the responding command message
                    AsyncMessageExt ext = new AsyncMessageExt();
                    ext.setClientId(clientId);
                    ext.setCorrelationId(msg.getMessageId());
                    ext.setDestination("Red5Chat");
                    ext.setBody(adapter.manage(msg));
                    //add as a child (body) of the command message
                    result.setBody(new Object[] { ext });

                    return result;
                } else {
                    log.debug("Adapter was not available");
                }
            }
            break;

        case Constants.SUBSCRIBE_OPERATION: //0
            log.debug("Subscribe: {}", clientId);
            //if there is a destination check for an adapter
            if (StringUtils.isNotBlank(destination)) {
                //look-up end-point and register
                if (endpoints.containsKey(destination)) {
                    Object endpoint = endpoints.get(destination);
                    //if the endpoint is an adapter, try to subscribe
                    if (endpoint instanceof ServiceAdapter) {
                        ServiceAdapter adapter = (ServiceAdapter) endpoint;
                        boolean subscribed = ((Boolean) adapter.manage(msg));
                        if (subscribed) {
                            log.debug("Client was subscribed");
                            registerClientToAdapter(clientId, adapter);
                        } else {
                            log.debug("Client was not subscribed");
                        }
                    }
                }
            }
            // Send back registration ok
            break;

        case Constants.UNSUBSCRIBE_OPERATION: //1
            log.trace("Unsubscribe: {}", clientId);
            if (registrations.containsKey(clientId)) {
                ServiceAdapter adapter = registrations.get(clientId);
                boolean unsubscribed = ((Boolean) adapter.manage(msg));
                if (unsubscribed) {
                    log.debug("Client was unsubscribed");
                    unregisterClientFromAdapter(clientId);
                } else {
                    log.debug("Client was not unsubscribed");
                }
            } else {
                log.debug("Client was not subscribed");
            }
            // Send back unregistration ok
            break;

        default:
            log.error("Unknown CommandMessage request: {}", msg);
            String errMsg = String.format("Don't know how to handle %s", msg);
            return returnError(msg, "notImplemented", errMsg, errMsg);
        }

        AcknowledgeMessage result = new AcknowledgeMessage();
        result.setBody(msg.getBody());
        result.setClientId(clientId);
        result.setCorrelationId(msg.getMessageId());
        result.setHeaders(headers);

        // put destination in ack if it exists
        if (StringUtils.isNotBlank(destination)) {
            result.setDestination(destination);
        }

        return result;
    }

    /**
     * Evaluate update requests sent by a client.
     * 
     * @param msg
     * @param event
     */
    @SuppressWarnings("unchecked")
    private void evaluateDataUpdate(DataMessage msg, DataMessage event) {
        switch (event.operation) {
        case Constants.DATA_OPERATION_UPDATE_ATTRIBUTES:
            List<Object> contents = (List<Object>) event.body;
            @SuppressWarnings("unused")
            List<String> attributeNames = (List<String>) contents.get(0);
            @SuppressWarnings("unused")
            Map<String, Object> oldValues = (Map<String, Object>) contents.get(1);
            @SuppressWarnings("unused")
            Map<String, Object> newValues = (Map<String, Object>) contents.get(2);
            /*
            // Commented out as it triggeres a crash in the compiler on Java 1.5
            for (@SuppressWarnings("unused") String name: attributeNames) {
               // TODO: store attribute change for registered clients
            }
            */
            break;

        default:
            log.error("Unknown data update request: {}", event);
        }
    }

    /**
     * Handle messages related to shared objects.
     * 
     * @param msg message
     * @return async message
     */
    @SuppressWarnings("unchecked")
    public AsyncMessage handleRequest(DataMessage msg) {
        log.debug("Handle DataMessage request");
        log.trace("{}", msg);
        setClientId(msg);
        SequencedMessage result = new SequencedMessage();
        result.clientId = msg.clientId;
        result.destination = msg.destination;
        result.correlationId = msg.messageId;
        switch (msg.operation) {
        case Constants.DATA_OPERATION_SET:
            result.body = new Object[] { msg.body };
            result.sequenceId = 0;
            result.sequenceSize = 1;
            // TODO: store initial version of object
            break;

        case Constants.DATA_OPERATION_UPDATE:
            for (DataMessage event : (List<DataMessage>) msg.body) {
                evaluateDataUpdate(msg, event);
            }
            AcknowledgeMessage res = new AcknowledgeMessage();
            res.clientId = msg.clientId;
            res.destination = msg.destination;
            res.correlationId = msg.messageId;
            res.body = msg.body;
            return res;

        default:
            log.error("Unknown DataMessage request: {}", msg);
            String errMsg = String.format("Don't know how to handle %s", msg);
            return returnError(msg, "notImplemented", errMsg, errMsg);

        }
        return result;
    }

    /**
     * Fallback method to handle arbitrary messages.
     * 
     * @param msg message
     * @return error message
     */
    public Message handleRequest(AbstractMessage msg) {
        log.debug("Handle AbstractMessage request");
        log.trace("{}", msg);
        setClientId(msg);

        Object endpoint = endpoints.get(msg.getDestination());
        log.debug("End point / destination: {}", endpoint);
        if (endpoint == null) {
            String errMsg = String.format("Endpoint %s doesn't exist.", msg.getDestination());
            log.debug("{} ({})", errMsg, msg);
            return returnError(msg, "Server.Invoke.Error", errMsg, errMsg);
        }

        //grab any headers
        Map<String, Object> headers = msg.getHeaders();
        log.debug("Headers: {}", headers);
        if (headers.containsKey(Message.FLEX_CLIENT_ID_HEADER)) {
            headers.remove(Message.FLEX_CLIENT_ID_HEADER);
        }
        if (headers.containsKey(Message.ENDPOINT_HEADER)) {
            headers.remove(Message.ENDPOINT_HEADER);
        }

        if (endpoint instanceof ServiceAdapter) {
            log.debug("Endpoint is a ServiceAdapter so message will be invoked");
            //prepare an ack message
            AcknowledgeMessage result = new AcknowledgeMessage();
            result.setClientId(msg.getClientId());
            result.setCorrelationId(msg.getMessageId());
            result.setDestination(msg.getDestination());
            result.setHeaders(headers);
            //get the adapter
            ServiceAdapter adapter = (ServiceAdapter) endpoint;
            //log.debug("Invoke: {}", adapter.invoke(msg));
            Object o = adapter.invoke(msg);
            //the result of the invocation will make up the message body      
            //AsyncMessage ext = new AsyncMessage();
            //ext.setClientId(msg.getClientId());
            //ext.setCorrelationId(result.getMessageId());
            //ext.setBody(o);

            result.setBody(new Object[] { o });
            return result;
        } else {
            log.error("Unknown Flex compatibility request: {}", msg);
            String errMsg = String.format("Don't know how to handle %s", msg);
            return returnError(msg, "notImplemented", errMsg, errMsg);
        }
    }

    /**
     * This is mandatory for client built from Flex 3 or later, or
     * client will hang with concurrent accesses.
     * @param msg
     */
    private void setClientId(AbstractMessage msg) {
        String clientId = msg.getClientId();
        if (clientId == null || "null".equals(clientId)) {
            log.trace("Dump: {}", msg);
            //use the connection client id before creating a new one
            IClient client = Red5.getConnectionLocal().getClient();
            if (client != null) {
                //should we format it?
                clientId = client.getId();
                msg.setClientId(RandomGUID.getPrettyFormatted(clientId));
            } else {
                msg.setClientId(UUID.randomUUID().toString());
            }
        }
    }

    /**
     * Maps a client to an adapter for lookups on messages that do not contain a destination.
     * 
     * @param clientId a subscribed client id
     * @param adapter service adapter to register for
     */
    private final void registerClientToAdapter(String clientId, ServiceAdapter adapter) {
        if (registrations == null) {
            registrations = new ConcurrentHashMap<String, ServiceAdapter>();
        }
        registrations.put(clientId, adapter);
    }

    /**
     * Removes a mapping for a client with an adapter.
     * 
     * @param clientId a subscribed client id
     */
    private final void unregisterClientFromAdapter(String clientId) {
        registrations.remove(clientId);
    }

}