package org.red5.server.net.remoting; /* * RED5 Open Source Flash Server - http://www.osflash.org/red5 * * Copyright (c) 2006-2009 by respective authors (see below). All rights reserved. * * This library is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any later * version. * * This library is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along * with this library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ import java.util.Collections; import java.util.List; import java.util.Map; 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.AsyncMessage; import org.red5.compatibility.flex.messaging.messages.CommandMessage; import org.red5.compatibility.flex.messaging.messages.Constants; import org.red5.compatibility.flex.messaging.messages.ErrorMessage; import org.red5.compatibility.flex.messaging.messages.RemotingMessage; import org.red5.io.utils.RandomGUID; import org.red5.server.api.service.IPendingServiceCall; import org.red5.server.api.service.IServiceInvoker; import org.red5.server.exception.ClientDetailsException; import org.red5.server.service.ConversionUtils; 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 (red5@osflash.org) * @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; /** * 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 <code>mx:RemoteObject</code> 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) { 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); if (endpoint == null) { String errMsg = String.format("Endpoint %s doesn't exist.", msg.destination); log.error("{} ({})", errMsg, msg); return returnError(msg, "Server.Invoke.Error", errMsg, errMsg); } Object[] args = (Object[]) ConversionUtils.convert(msg.body, Object[].class); IPendingServiceCall call = new PendingCall(msg.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. AcknowledgeMessage result = new AcknowledgeMessage(); result.body = call.getResult(); result.headers = msg.headers; result.clientId = msg.clientId; result.correlationId = msg.messageId; return result; } /** * Handle command message request. * * @param msg message * @return async message */ public AsyncMessage handleRequest(CommandMessage msg) { setClientId(msg); AsyncMessage result = null; switch (msg.operation) { case Constants.OPERATION_PING: // Send back pong message result = new AcknowledgeMessage(); result.clientId = msg.clientId; result.correlationId = msg.messageId; break; case Constants.OPERATION_REGISTER: // Send back registration ok result = new AcknowledgeMessage(); result.clientId = msg.clientId; result.correlationId = msg.messageId; // TODO: store client id and destination to send further updates break; case Constants.OPERATION_POLL: // Send back modifications result = new AcknowledgeMessage(); result.clientId = msg.clientId; result.correlationId = msg.messageId; result.destination = msg.destination; // TODO: send back stored updates for this client break; default: log.error("Unknown CommandMessage request: {}", msg); String errMsg = String.format("Don't know how to handle %s", msg); result = returnError(msg, "notImplemented", errMsg, errMsg); } 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) { 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 ErrorMessage handleRequest(AbstractMessage msg) { setClientId(msg); 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) { if (msg.clientId == null) { msg.clientId = new RandomGUID().toString(); } } }