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 */
022 package org.granite.messaging.service;
023
024 import java.lang.reflect.InvocationTargetException;
025 import java.util.ArrayList;
026 import java.util.List;
027
028 import org.granite.clustering.TransientReference;
029 import org.granite.config.GraniteConfig;
030 import org.granite.config.flex.Destination;
031 import org.granite.context.GraniteContext;
032 import org.granite.logging.Logger;
033 import org.granite.messaging.service.security.RemotingDestinationSecurizer;
034
035 import 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
048 public 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 }