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
021 package org.granite.messaging.service;
022
023 import java.lang.reflect.InvocationTargetException;
024 import java.util.ArrayList;
025 import java.util.List;
026
027 import org.granite.clustering.TransientReference;
028 import org.granite.config.GraniteConfig;
029 import org.granite.config.flex.Destination;
030 import org.granite.context.GraniteContext;
031 import org.granite.logging.Logger;
032 import org.granite.messaging.service.security.RemotingDestinationSecurizer;
033
034 import 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
047 public 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 beforeInvocation(invocationContext);
195 if (invocationListeners != null) {
196 for (ServiceInvocationListener invocationListener : invocationListeners)
197 invocationListener.beforeInvocation(invocationContext);
198 }
199
200 log.debug(">> Invoking method: %s with ", invocationContext.getMethod(), args);
201
202 Throwable error = null;
203 Object result = null;
204 try {
205
206 // Check security 1 (destination).
207 if (destination.getSecurizer() instanceof RemotingDestinationSecurizer)
208 ((RemotingDestinationSecurizer)destination.getSecurizer()).canExecute(invocationContext);
209
210 boolean retry = false;
211 try {
212 // Check security 2 (security service).
213 if (config.hasSecurityService())
214 result = config.getSecurityService().authorize(invocationContext);
215 else
216 result = invocationContext.invoke();
217 } catch (Exception e) {
218 if (retryInvocation(invocationContext, (e instanceof InvocationTargetException ? e.getCause() : e)))
219 retry = true;
220 else
221 throw e;
222 }
223
224 if (retry) {
225 // Check security 2 (security service).
226 if (config.hasSecurityService())
227 result = config.getSecurityService().authorize(invocationContext);
228 else
229 result = invocationContext.invoke();
230 }
231
232 } catch (InvocationTargetException e) {
233 error = e.getTargetException();
234 } catch (Throwable e) {
235 error = e;
236 } finally {
237 if (error != null) {
238 afterInvocationError(invocationContext, error);
239 if (invocationListeners != null) {
240 for (ServiceInvocationListener invocationListener : invocationListeners)
241 invocationListener.afterInvocationError(invocationContext, error);
242 }
243 throw serviceExceptionHandler.handleInvocationException(invocationContext, error);
244 }
245 }
246
247 result = afterInvocation(invocationContext, result);
248 if (invocationListeners != null) {
249 for (ServiceInvocationListener invocationListener : invocationListeners)
250 result = invocationListener.afterInvocation(invocationContext, result);
251 }
252
253 log.debug("<< Returning result: %s", result);
254
255 return result;
256 }
257 }