/* license-start * * Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 3. * * This program 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 General Public License for more details, at <http://www.gnu.org/licenses/>. * * Contributors: * Crispico - Initial API and implementation * * license-end */ package org.flowerplatform.communication.service; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.flowerplatform.communication.CommunicationPlugin; import org.flowerplatform.communication.callback.InvokeCallbackClientCommand; import org.flowerplatform.communication.channel.CommunicationChannel; import org.flowerplatform.communication.command.AbstractClientCommand; import org.flowerplatform.communication.command.AbstractServerCommand; import org.flowerplatform.communication.command.CompoundClientCommand; import org.flowerplatform.communication.command.DisplaySimpleMessageClientCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class encapsulates the service method invocation logic. * * <p> * The corresponding service is found by the ID in the registry ({@link ServiceRegistry}). * The corresponding method is invoked by reflection. * * <p> * If {@link #getCallbackId()} <code> == 0</code>, that means that the Flex client * won't be called back (i.e. it has is no callback registered/pending for answer). * * <p> * If the service method has the first parameter of type {@link ServiceInvocationContext}, * it will receive an object of this type during the call followed by the rest of the parameters. * * <p> * If there is an exception (service/method not found or the invoked method threw an exception), * it is logged and a message is sent to the client (using {@link DisplaySimpleMessageClientCommand}). * In this case, if the client was waiting for a callback, it will receive a command that will * dispose that callback from the memory (but the handler won't be called). * * <p> * <strong>IMPORTANT NOTE:</strong> The service mechanism is a replacement/short hand of the * original command system. The advantage is that there is less code to write (e.g. 3 methods * instead of 3 + 3 classes). The disadvantage is that there is no type checking (on the Flex side). * So <strong>please pay special attention when the signature of a service method changes</strong>, * because you won't get any compiler error. Look carefully in the whole workspace (using CTRL + H * and the name of the service or method), in order to modify ALL places that invokes that particular * method. * * @see ServiceRegistry * * @author Cristi * */ public class InvokeServiceMethodServerCommand extends AbstractServerCommand { private static Logger logger = LoggerFactory.getLogger(InvokeServiceMethodServerCommand.class); /** * @see Getter doc. * * */ private String serviceId; /** * @see Getter doc. * * */ private String methodName; /** * @see Getter doc. * * */ private List<Object> parameters; /** * @see Getter doc. * * */ private long callbackId; private long exceptionCallbackId; private Map<String, Object> additionalDataForServiceInvocationContext; /** * The string service id of the service, as registered in {@link ServiceRegistry}. * * */ public String getServiceId() { return serviceId; } /** * */ public void setServiceId(String serviceId) { this.serviceId = serviceId; } /** * The name of the method to invoke. * * */ public String getMethodName() { return methodName; } /** * */ public void setMethodName(String methodName) { this.methodName = methodName; } /** * The list of parameters, sent from the Flex client. * * */ public List<Object> getParameters() { return parameters; } /** * */ public void setParameters(List<Object> parameters) { this.parameters = parameters; } /** * The id of the callback. May be 0, meaning that * the client shouldn't be called back with a result. * * */ public long getCallbackId() { return callbackId; } /** * */ public void setCallbackId(long callbackId) { this.callbackId = callbackId; } public long getExceptionCallbackId() { return exceptionCallbackId; } public void setExceptionCallbackId(long exceptionCallbackId) { this.exceptionCallbackId = exceptionCallbackId; } public Map<String, Object> getAdditionalDataForServiceInvocationContext() { return additionalDataForServiceInvocationContext; } public void setAdditionalDataForServiceInvocationContext(Map<String, Object> additionalDataForServiceInvocationContext) { this.additionalDataForServiceInvocationContext = additionalDataForServiceInvocationContext; } /** * @see Class documentation. * * */ public void executeCommand() { Object result = null; AbstractClientCommand commandToSendBecauseException = null; try { // find the service Object service = CommunicationPlugin.getInstance().getServiceRegistry().getService(getServiceId()); if (service == null) throw new RuntimeException(String.format("The service with id='%s' was not found in the service registry.", getServiceId())); // find the method Method foundMethod = null; for (Method method : service.getClass().getMethods()) if (method.getName().equals(getMethodName())) { foundMethod = method; break; } if (foundMethod == null) throw new RuntimeException(String.format("The service with id='%s' doesn't contain the method '%s'", getServiceId(), getMethodName())); // check to see if the first parameter is a ServiceInvocationContext Type[] parameterTypes = foundMethod.getGenericParameterTypes(); if (parameterTypes.length > 0 && parameterTypes[0] instanceof Class<?> && ServiceInvocationContext.class.isAssignableFrom((Class<?>) parameterTypes[0])) { // in this case, we inject a ServiceInvocationContext ServiceInvocationContext context = createServiceInvocationContext(getCommunicationChannel(), this); context.setAdditionalData(getAdditionalDataForServiceInvocationContext()); if (getParameters() == null) setParameters(new ArrayList<Object>()); if (!(getParameters().size() > 1 && getParameters().get(0) instanceof ServiceInvocationContext)) { getParameters().add(0, context); } } // invoke the method result = foundMethod.invoke(service, getParameters() != null ? getParameters().toArray() : null); } catch (Throwable e) { logger.error(String.format("Exception caught while invoking service %s, method %s", getServiceId(), getMethodName()), e); if (e instanceof InvocationTargetException && e.getCause() != null) e = e.getCause(); commandToSendBecauseException = new DisplaySimpleMessageClientCommand( "Service method invocation error", String.format("Invocation of the service '%s', method '%s' failed, throwing exception:\n '%s'\n Please see the error log for additional information and stacktrace.", getServiceId(), getMethodName(), e.toString()), DisplaySimpleMessageClientCommand.ICON_ERROR); } AbstractClientCommand commandToSend = null; if (commandToSendBecauseException != null) { if (getCallbackId() == 0 && getExceptionCallbackId() == 0) { // no callback expected; we send only the message commandToSend = commandToSendBecauseException; } else { // a callback was expected CompoundClientCommand compoundCommand = new CompoundClientCommand().appendCommand(commandToSendBecauseException); commandToSend = compoundCommand; if (getCallbackId() != 0) { compoundCommand.appendCommand(new InvokeCallbackClientCommand(getCallbackId(), null, true)); } if (getExceptionCallbackId() != 0) { // we don't send the error because a quick look showed that it's not serialized correctly // e.g. no name, cause, etc => useless on client compoundCommand.appendCommand(new InvokeCallbackClientCommand(getExceptionCallbackId(), null)); } } } else { if (getCallbackId() != 0) { // normal execution and callback expected commandToSend = new InvokeCallbackClientCommand(getCallbackId(), result); } if (getExceptionCallbackId() != 0) { AbstractClientCommand removeExceptionCallbackCommand = new InvokeCallbackClientCommand(getExceptionCallbackId(), null, true); if (commandToSend == null) { commandToSend = removeExceptionCallbackCommand; } else { commandToSend = new CompoundClientCommand(). appendCommand(commandToSend). appendCommand(removeExceptionCallbackCommand); } } } if (commandToSend != null) { // send back the result getCommunicationChannel().appendCommandToCurrentHttpResponse(commandToSend); } } protected ServiceInvocationContext createServiceInvocationContext(CommunicationChannel communicationChannel, InvokeServiceMethodServerCommand command) { return new ServiceInvocationContext(communicationChannel, command); } @Override public String toString() { return "InvokeServiceMethodServerCommand with service=" + serviceId + " and method=" + methodName; } }