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