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    }