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}