package org.red5.server.service;
/*
* 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.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.IScope;
import org.red5.server.api.Red5;
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.debug("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.debug("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();
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]);
argsWithConnection[i + 1] = args[i];
}
} else {
argsWithConnection = new Object[] { conn };
}
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("you are not allowed to execute this method");
}
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("you are not allowed to execute this method");
}
}
log.debug("Invoking method: {}", method.toString());
if (method.getReturnType() == Void.class) {
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);
log.error("Service invocation error", invocationEx);
}
return false;
} catch (Exception ex) {
call.setException(ex);
call.setStatus(Call.STATUS_GENERAL_EXCEPTION);
log.error("Error executing call: {}", call);
log.error("Service invocation error", ex);
return false;
}
return true;
}
}