001/* 002 GRANITE DATA SERVICES 003 Copyright (C) 2011 GRANITE DATA SERVICES S.A.S. 004 005 This file is part of Granite Data Services. 006 007 Granite Data Services is free software; you can redistribute it and/or modify 008 it under the terms of the GNU Library General Public License as published by 009 the Free Software Foundation; either version 2 of the License, or (at your 010 option) any later version. 011 012 Granite Data Services is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License 015 for more details. 016 017 You should have received a copy of the GNU Library General Public License 018 along with this library; if not, see <http://www.gnu.org/licenses/>. 019*/ 020 021package org.granite.messaging.service; 022 023import java.lang.reflect.InvocationTargetException; 024import java.util.ArrayList; 025import java.util.List; 026 027import org.granite.clustering.TransientReference; 028import org.granite.config.GraniteConfig; 029import org.granite.config.flex.Destination; 030import org.granite.context.GraniteContext; 031import org.granite.logging.Logger; 032import org.granite.messaging.service.security.RemotingDestinationSecurizer; 033 034import flex.messaging.messages.RemotingMessage; 035 036/** 037 * Abstract base class for all service's methods calls. This class mainly implements a final 038 * invocation method which deals with parameter conversions, security and listeners. 039 * 040 * @author Franck WOLFF 041 * 042 * @see ServiceFactory 043 * @see ServiceInvocationListener 044 * @see ServiceExceptionHandler 045 */ 046@TransientReference 047public abstract class ServiceInvoker<T extends ServiceFactory> { 048 049 private static final Logger log = Logger.getLogger(ServiceInvoker.class); 050 051 protected final List<ServiceInvocationListener> invocationListeners; 052 protected final Destination destination; 053 protected final T factory; 054 protected Object invokee = null; 055 056 private ServiceExceptionHandler serviceExceptionHandler; 057 058 /** 059 * Constructs a new ServiceInvoker. This constructor is used by a dedicated {@link ServiceFactory}. 060 * 061 * @param destination the remote destination of this service (services-config.xml). 062 * @param factory the factory that have called this constructor. 063 * @throws ServiceException if anything goes wrong. 064 */ 065 protected ServiceInvoker(Destination destination, T factory) throws ServiceException { 066 this.destination = destination; 067 this.factory = factory; 068 this.serviceExceptionHandler = factory.getServiceExceptionHandler(); 069 070 ServiceInvocationListener invocationListener = 071 GraniteContext.getCurrentInstance().getGraniteConfig().getInvocationListener(); 072 if (invocationListener != null) { 073 this.invocationListeners = new ArrayList<ServiceInvocationListener>(); 074 this.invocationListeners.add(invocationListener); 075 } else 076 this.invocationListeners = null; 077 } 078 079 /** 080 * Called at the beginning of the {@link #invoke(RemotingMessage)} method. Give a chance to modify the 081 * the services (invokee) about to be called. Does nothing by default. The default invokee object is 082 * created by actual implementations of this abstract class. 083 * 084 * @param request the current remoting message (sent from Flex). 085 * @param methodName the name of the method to be called. 086 * @param args the method parameter values. 087 * @return the (possibly adjusted) invokee object. 088 * @throws ServiceException if anything goes wrong. 089 */ 090 protected Object adjustInvokee(RemotingMessage request, String methodName, Object[] args) throws ServiceException { 091 return invokee; 092 } 093 094 /** 095 * Called before the {@link #invoke(RemotingMessage)} method starts to search for a method named <tt>methodName</tt> 096 * with the arguments <tt>args</tt> on the invokee object. Give a chance to modify the method name or the paramaters. 097 * Does nothing by default. 098 * 099 * @param invokee the service instance used for searching the method with the specified arguments. 100 * @param methodName the method name. 101 * @param args the arguments of the method. 102 * @return an array of containing the (possibly adjusted) method name and its arguments. 103 */ 104 protected Object[] beforeMethodSearch(Object invokee, String methodName, Object[] args) { 105 return new Object[] { methodName, args }; 106 } 107 108 /** 109 * Called before the invocation of the services method. Does nothing by default. 110 * 111 * @param context the current invocation context. 112 */ 113 protected void beforeInvocation(ServiceInvocationContext context) { 114 } 115 116 /** 117 * Called after a failed invocation of the service's method. Returns <tt>false</tt> by default. 118 * 119 * @param context the current invocation context. 120 * @param t the exception that caused the invocation failure. 121 * @return <tt>true</tt> if invocation should be retried, <tt>false</tt> otherwise. 122 */ 123 protected boolean retryInvocation(ServiceInvocationContext context, Throwable t) { 124 return false; 125 } 126 127 /** 128 * Called after a failed invocation of the service's method, possibly after a new attempt (see 129 * {@link #retryInvocation(ServiceInvocationContext, Throwable)}. Does nothing by default. 130 * 131 * @param context the current invocation context. 132 * @param error the exception that caused the invocation failure. 133 */ 134 protected void afterInvocationError(ServiceInvocationContext context, Throwable error) { 135 } 136 137 /** 138 * Called after a successful invocation of the service's method. Does nothing by default. 139 * 140 * @param context the current invocation context. 141 * @param result the result of the invocation (returned by the called method). 142 */ 143 protected Object afterInvocation(ServiceInvocationContext context, Object result) { 144 return result; 145 } 146 147 /** 148 * Call a service's method according to the informations contained in the given remoting message. 149 * This method is final and implements a standard way of calling a service's method, independent of 150 * the underlying framework (EJB3, Spring, Seam, etc.) It deals with security, parameter conversions, 151 * exception handling and offers several ways of listening (and possibly adjusting) the invocation 152 * process. 153 * 154 * @param request the remoting message containing informations about the call. 155 * @return the result of the service's method invocation. 156 * @throws ServiceException if anything goes wrong (security, invocation target exception, etc.) 157 */ 158 public final Object invoke(RemotingMessage request) throws ServiceException { 159 160 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig(); 161 162 String methodName = request.getOperation(); 163 Object[] args = (Object[])request.getBody(); 164 165 // Adjust invokee (SimpleServiceInvoker with runtime "source" attribute) 166 Object invokee = adjustInvokee(request, methodName, args); 167 168 // Try to find out method to call (and convert parameters). 169 Object[] call = beforeMethodSearch(invokee, methodName, args); 170 methodName = (String)call[0]; 171 args = (Object[])call[1]; 172 if (invocationListeners != null) { 173 for (ServiceInvocationListener invocationListener : invocationListeners) 174 args = invocationListener.beforeMethodSearch(invokee, methodName, args); 175 } 176 177 log.debug(">> Trying to find method: %s%s in %s", methodName, args, invokee != null ? invokee.getClass() : ""); 178 179 ServiceInvocationContext invocationContext = null; 180 try { 181 invocationContext = config.getMethodMatcher().findServiceMethod( 182 request, 183 destination, 184 invokee, 185 methodName, 186 args 187 ); 188 } catch (NoSuchMethodException e) { 189 throw serviceExceptionHandler.handleNoSuchMethodException( 190 request, destination, invokee, methodName, args, e 191 ); 192 } 193 194 try { 195 beforeInvocation(invocationContext); 196 if (invocationListeners != null) { 197 for (ServiceInvocationListener invocationListener : invocationListeners) 198 invocationListener.beforeInvocation(invocationContext); 199 } 200 } 201 catch (Exception error) { 202 handleInvocationError(invocationContext, error); 203 } 204 205 log.debug(">> Invoking method: %s with ", invocationContext.getMethod(), args); 206 207 Throwable error = null; 208 Object result = null; 209 try { 210 211 // Check security 1 (destination). 212 if (destination.getSecurizer() instanceof RemotingDestinationSecurizer) 213 ((RemotingDestinationSecurizer)destination.getSecurizer()).canExecute(invocationContext); 214 215 boolean retry = false; 216 try { 217 // Check security 2 (security service). 218 if (config.hasSecurityService() && config.getSecurityService().acceptsContext()) 219 result = config.getSecurityService().authorize(invocationContext); 220 else 221 result = invocationContext.invoke(); 222 } 223 catch (Exception e) { 224 if (retryInvocation(invocationContext, (e instanceof InvocationTargetException ? e.getCause() : e))) 225 retry = true; 226 else 227 throw e; 228 } 229 230 if (retry) { 231 // Check security 2 (security service). 232 if (config.hasSecurityService()) 233 result = config.getSecurityService().authorize(invocationContext); 234 else 235 result = invocationContext.invoke(); 236 } 237 238 } catch (InvocationTargetException e) { 239 error = e.getTargetException(); 240 } catch (Throwable e) { 241 error = e; 242 } finally { 243 if (error != null) { 244 handleInvocationError(invocationContext, error); 245 } 246 } 247 248 result = afterInvocation(invocationContext, result); 249 if (invocationListeners != null) { 250 for (ServiceInvocationListener invocationListener : invocationListeners) 251 result = invocationListener.afterInvocation(invocationContext, result); 252 } 253 254 log.debug("<< Returning result: %s", result); 255 256 return result; 257 } 258 259 260 private void handleInvocationError(ServiceInvocationContext invocationContext, Throwable error) throws ServiceException { 261 afterInvocationError(invocationContext, error); 262 if (invocationListeners != null) { 263 for (ServiceInvocationListener invocationListener : invocationListeners) 264 invocationListener.afterInvocationError(invocationContext, error); 265 } 266 if (error instanceof ServiceException) 267 throw (ServiceException)error; 268 throw serviceExceptionHandler.handleInvocationException(invocationContext, error); 269 } 270}