/* * RED5 Open Source Flash Server - http://code.google.com/p/red5/ * * Copyright 2006-2012 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.service; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.red5.annotations.DeclarePrivate; import org.red5.annotations.DeclareProtected; import org.red5.server.api.IConnection; import org.red5.server.api.Red5; import org.red5.server.api.scope.IScope; import org.red5.server.api.service.IPendingServiceCall; import org.red5.server.api.service.IServiceCall; import org.red5.server.api.service.IServiceInvoker; import org.red5.server.exception.ClientDetailsException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Makes remote calls, invoking services, resolves service handlers * * @author The Red5 Project (red5@osflash.org) * @author Luke Hubbard, Codegent Ltd (luke@codegent.com) */ public class ServiceInvoker implements IServiceInvoker { /** * Logger */ private static final Logger log = LoggerFactory.getLogger(ServiceInvoker.class); /** * Service name */ public static final String SERVICE_NAME = "serviceInvoker"; /** * Service resolvers set */ private Set<IServiceResolver> serviceResolvers = new HashSet<IServiceResolver>(); /** * Setter for service resolvers. * * @param resolvers Service resolvers */ public void setServiceResolvers(Set<IServiceResolver> resolvers) { serviceResolvers = resolvers; } /** * Lookup a handler for the passed service name in the given scope. * * @param scope * Scope * @param serviceName * Service name * @return Service handler */ private Object getServiceHandler(IScope scope, String serviceName) { // Get application scope handler first Object service = scope.getHandler(); if (serviceName == null || serviceName.equals("")) { // No service requested, return application scope handler return service; } // Search service resolver that knows about service name for (IServiceResolver resolver : serviceResolvers) { service = resolver.resolveService(scope, serviceName); if (service != null) { return service; } } // Requested service does not exist. return null; } /** {@inheritDoc} */ public boolean invoke(IServiceCall call, IScope scope) { String serviceName = call.getServiceName(); log.trace("Service name {}", serviceName); Object service = getServiceHandler(scope, serviceName); if (service == null) { // Exception must be thrown if service was not found call.setException(new ServiceNotFoundException(serviceName)); // Set call status call.setStatus(Call.STATUS_SERVICE_NOT_FOUND); log.warn("Service not found: {}", serviceName); return false; } else { log.trace("Service found: {}", serviceName); } // Invoke if everything is ok return invoke(call, service); } /** {@inheritDoc} */ public boolean invoke(IServiceCall call, Object service) { IConnection conn = Red5.getConnectionLocal(); String methodName = call.getServiceMethodName(); log.debug("Service: {} name: {} method: {}", new Object[] { service, call.getServiceName(), methodName }); // pull off the prefixes since java doesnt allow this on a method name if (methodName.charAt(0) == '@') { log.debug("Method name contained an illegal prefix, it will be removed: {}", methodName); methodName = methodName.substring(1); } // build an array with the incoming args and the current connection as the first element Object[] args = call.getArguments(); Object[] argsWithConnection; if (args != null) { argsWithConnection = new Object[args.length + 1]; argsWithConnection[0] = conn; for (int i = 0; i < args.length; i++) { log.debug("{} => {}", i, args[i]); if (args[i] != null) { log.trace("Arg type: {}", args[i].getClass().getName()); } argsWithConnection[i + 1] = args[i]; } } else { argsWithConnection = new Object[] { conn }; } // find the method Object[] methodResult = null; // First, search for method with the connection as first parameter. methodResult = ServiceUtils.findMethodWithExactParameters(service, methodName, argsWithConnection); if (methodResult.length == 0 || methodResult[0] == null) { // Second, search for method without the connection as first parameter. methodResult = ServiceUtils.findMethodWithExactParameters(service, methodName, args); if (methodResult.length == 0 || methodResult[0] == null) { // Third, search for method with the connection as first parameter in a list argument. methodResult = ServiceUtils.findMethodWithListParameters(service, methodName, argsWithConnection); if (methodResult.length == 0 || methodResult[0] == null) { // Fourth, search for method without the connection as first parameter in a list argument. methodResult = ServiceUtils.findMethodWithListParameters(service, methodName, args); if (methodResult.length == 0 || methodResult[0] == null) { log.error("Method {} with parameters {} not found in {}", new Object[] { methodName, (args == null ? Collections.EMPTY_LIST : Arrays.asList(args)), service }); call.setStatus(Call.STATUS_METHOD_NOT_FOUND); if (args != null && args.length > 0) { call.setException(new MethodNotFoundException(methodName, args)); } else { call.setException(new MethodNotFoundException(methodName)); } return false; } } } } Object result = null; Method method = (Method) methodResult[0]; Object[] params = (Object[]) methodResult[1]; try { if (method.isAnnotationPresent(DeclarePrivate.class)) { // Method may not be called by clients. log.debug("Method {} is declared private.", method); throw new NotAllowedException("Access denied, method is private"); } final DeclareProtected annotation = method.getAnnotation(DeclareProtected.class); if (annotation != null) { if (!conn.getClient().hasPermission(conn, annotation.permission())) { // client doesn't have required permission log.debug("Client {} doesn't have required permission {} to call {}", new Object[] { conn.getClient(), annotation.permission(), method }); throw new NotAllowedException("Access denied, method is protected"); } } log.debug("Invoking method: {}", method.toString()); if (method.getReturnType().equals(Void.TYPE)) { log.debug("result: void"); method.invoke(service, params); call.setStatus(Call.STATUS_SUCCESS_VOID); } else { result = method.invoke(service, params); log.debug("result: {}", result); call.setStatus(result == null ? Call.STATUS_SUCCESS_NULL : Call.STATUS_SUCCESS_RESULT); } if (call instanceof IPendingServiceCall) { ((IPendingServiceCall) call).setResult(result); } } catch (NotAllowedException e) { call.setException(e); call.setStatus(Call.STATUS_ACCESS_DENIED); return false; } catch (IllegalAccessException accessEx) { call.setException(accessEx); call.setStatus(Call.STATUS_ACCESS_DENIED); log.error("Error executing call: {}", call); log.error("Service invocation error", accessEx); return false; } catch (InvocationTargetException invocationEx) { call.setException(invocationEx); call.setStatus(Call.STATUS_INVOCATION_EXCEPTION); if (!(invocationEx.getCause() instanceof ClientDetailsException)) { // only log if not handled by client log.error("Error executing call: {}", call, invocationEx); } return false; } catch (Exception ex) { call.setException(ex); call.setStatus(Call.STATUS_GENERAL_EXCEPTION); log.error("Error executing call: {}", call, ex); return false; } return true; } }