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    }