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.tide;
023
024import java.lang.reflect.Constructor;
025import java.lang.reflect.InvocationTargetException;
026import java.lang.reflect.Method;
027import java.util.Map;
028import java.util.Set;
029
030import javax.servlet.http.HttpSession;
031
032import org.granite.config.flex.Destination;
033import org.granite.context.GraniteContext;
034import org.granite.logging.Logger;
035import org.granite.messaging.amf.io.convert.Converters;
036import org.granite.messaging.service.ServiceException;
037import org.granite.messaging.service.ServiceFactory;
038import org.granite.messaging.service.ServiceInvocationContext;
039import org.granite.messaging.service.ServiceInvoker;
040import org.granite.messaging.service.security.SecurityServiceException;
041import org.granite.messaging.webapp.HttpGraniteContext;
042import org.granite.tide.data.DataContext;
043import org.granite.tide.data.DataEnabled;
044import org.granite.tide.data.DataMergeContext;
045import org.granite.tide.data.DataEnabled.PublishMode;
046import org.granite.tide.validators.EntityValidator;
047import org.granite.tide.validators.InvalidValue;
048import org.granite.util.TypeUtil;
049
050import flex.messaging.messages.RemotingMessage;
051
052 
053/**
054 * Base class for Tide service invokers
055 * Adapts the Tide invocation model with Granite
056 * 
057 * @author William DRAI
058 */
059public class TideServiceInvoker<T extends ServiceFactory> extends ServiceInvoker<T> {
060     
061    private static final Logger log = Logger.getLogger(TideServiceInvoker.class);
062    
063    public static final String VALIDATOR_KEY = "org.granite.tide.validator.key";
064    public static final String VALIDATOR_NOT_AVAILABLE = "org.granite.tide.validator.notAvailable";
065    
066    public static final String VALIDATOR_NAME = "validator-name";
067    public static final String VALIDATOR_CLASS_NAME = "validator-class-name";
068    
069    /**
070     * Current tide context
071     */
072    private TideServiceContext tideContext = null;
073    
074    private transient EntityValidator validator = null;
075    
076    
077    public TideServiceInvoker(Destination destination, T factory) throws ServiceException {
078        super(destination, factory);
079        this.invokee = this;
080        this.tideContext = lookupContext();
081        this.tideContext.initCall();
082        initValidator();
083    }
084    
085    public TideServiceInvoker(Destination destination, T factory, TideServiceContext tideContext) throws ServiceException {
086        super(destination, factory);
087        this.invokee = this;
088        this.tideContext = tideContext;
089        this.tideContext.initCall();
090        initValidator();
091    }
092    
093    
094    public Object initializeObject(Object parent, String[] propertyNames) {
095        return tideContext.lazyInitialize(parent, propertyNames);
096    }
097    
098    
099    private static final InvalidValue[] EMPTY_INVALID_VALUES = new InvalidValue[0];
100    
101    protected void initValidator() {
102        Map<String, Object> applicationMap = GraniteContext.getCurrentInstance().getApplicationMap();
103        Boolean validatorNotAvailable = (Boolean)applicationMap.get(VALIDATOR_NOT_AVAILABLE);
104        validator = (EntityValidator)applicationMap.get(VALIDATOR_KEY);
105        
106        if (validator != null || Boolean.TRUE.equals(validatorNotAvailable))
107                return;
108        
109        String className = this.destination.getProperties().get(VALIDATOR_CLASS_NAME);
110        if (className != null) {
111                initValidatorWithClassName(className, null);
112                if (validator == null) {
113                        log.warn("Validator class " + className + " not found: validation not enabled");
114                    applicationMap.put(VALIDATOR_NOT_AVAILABLE, Boolean.TRUE);
115                }
116                else {
117                        log.info("Validator class " + className + " initialized");
118                        applicationMap.put(VALIDATOR_KEY, validator);
119                }
120        }
121        else {
122                String name = this.destination.getProperties().get(VALIDATOR_NAME);
123                if (name != null) {
124                        try {
125                                validator = (EntityValidator)tideContext.findComponent(name, EntityValidator.class);
126                        }
127                        catch (ServiceException e) {
128                                name = null;
129                        }
130                }
131                
132                if (validator == null) {
133                        className = "org.granite.tide.validation.BeanValidation";                       
134                        initValidatorWithClassName(className, "javax.validation.ValidatorFactory");
135                }
136                
137                if (validator == null) {
138                        if (name != null)
139                                log.warn("Validator component " + name + " not found: validation not enabled");
140                        else
141                                log.warn("Validator class " + className + " not found: validation not enabled");
142                        
143                        applicationMap.put(VALIDATOR_NOT_AVAILABLE, Boolean.TRUE);
144                }
145                else {
146                        log.info("Validator class " + validator.getClass().getName() + " initialized");
147                        applicationMap.put(VALIDATOR_KEY, validator);
148                }
149        }
150    }
151    
152    private void initValidatorWithClassName(String className, String constructorArgClassName) {
153        try {
154                Object constructorArg = null;
155                Class<?> constructorArgClass = null;
156                if (constructorArgClassName != null) {
157                        try {
158                                constructorArgClass = TypeUtil.forName(constructorArgClassName);
159                                constructorArg = tideContext.findComponent(null, constructorArgClass);
160                        }
161                        catch (Exception e) {
162                                // Constructor arg not found 
163                        }
164                }
165                
166            Class<?> validatorClass = Thread.currentThread().getContextClassLoader().loadClass(className);
167            try {
168                Constructor<?> c = validatorClass.getConstructor(constructorArgClass);
169                validator = (EntityValidator)c.newInstance(constructorArg);
170            }
171            catch (NoSuchMethodException e) {                   
172                validator = (EntityValidator)validatorClass.newInstance();
173            }
174            catch (InvocationTargetException e) {
175                log.error(e, "Could not initialize Tide validator " + className + " with argument of type " + constructorArgClassName);
176            }
177        }
178        catch (ClassNotFoundException e) {
179            // Ignore: Hibernate Validator not present
180        }
181        catch (NoClassDefFoundError e) {
182            // Ignore: Hibernate Validator not present
183        }
184        catch (IllegalAccessException e) {
185            // Ignore: Hibernate Validator not present
186        }
187        catch (InstantiationException e) {
188            // Ignore: Hibernate Validator not present
189        }
190    }
191    
192    
193    public InvalidValue[] validateObject(Object entity, String propertyName, Object value) {
194        initValidator();
195        if (entity != null && validator != null)
196            return validator.getPotentialInvalidValues(entity.getClass(), propertyName, value);
197        
198        return EMPTY_INVALID_VALUES;
199    }
200
201    
202    public void login() {
203    }
204    
205    public void logout() {
206        HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
207        HttpSession session = context.getSession(false);
208        if (session != null)
209            session.invalidate();
210    }
211    
212    public void resyncContext() {
213    }
214    
215    
216    protected TideServiceContext lookupContext() {
217        return null;
218    }
219    
220    protected TideServiceContext getTideContext() {
221        return tideContext;
222    }
223    
224    
225    
226    @Override
227    protected Object adjustInvokee(RemotingMessage request, String methodName, Object[] args) throws ServiceException {
228        if ("invokeComponent".equals(methodName)) {
229            String componentName = (String)args[0];
230            String componentClassName = (String)args[1];
231            Class<?> componentClass = null;
232            try {
233                    if (componentClassName != null)
234                        componentClass = TypeUtil.forName(componentClassName);
235            }
236            catch (ClassNotFoundException e) {
237                throw new ServiceException("Component class not found " + componentClassName, e);
238            }
239            log.debug("Setting invokee to %s", componentName);
240            
241            Object instance = tideContext.findComponent(componentName, componentClass);
242            Set<Class<?>> componentClasses = instance != null ? tideContext.findComponentClasses(componentName, componentClass) : null;
243            
244            GraniteContext context = GraniteContext.getCurrentInstance();
245            if (instance != null && componentClasses != null && context.getGraniteConfig().isComponentTideEnabled(componentName, componentClasses, instance))
246                return tideContext.adjustInvokee(instance, componentName, componentClasses);
247            
248            if (instance != null)
249                log.error("SECURITY CHECK: Remote call refused to a non Tide-enabled component: " + componentName + "." + args[1] + ", class: " + componentClasses + ", instance: " + instance);
250            throw SecurityServiceException.newAccessDeniedException("Component [" + componentName + (componentClassName != null ? " of class " + componentClassName : "") + "] not found");
251        }
252        
253        return super.adjustInvokee(request, methodName, args);
254    }
255    
256
257    @Override
258    protected Object[] beforeMethodSearch(Object invokee, String methodName, Object[] args) {
259        if ("invokeComponent".equals(methodName)) { 
260                return tideContext.beforeMethodSearch(invokee, methodName, args);
261        } 
262        else if ("initializeObject".equals(methodName)) {
263                return new Object[] { methodName, new Object[] { args[0], args[1] } };
264        } 
265        else if ("validateObject".equals(methodName)) {
266            return new Object[] { methodName, new Object[] { args[0], args[1], args[2] } };
267        }
268                
269        return new Object[] { methodName, new Object[] {} };
270    }
271
272    
273    private static final String DATAENABLED_HANDLED = "org.granite.tide.invoker.dataEnabled";
274
275    @Override
276    protected void beforeInvocation(ServiceInvocationContext context) {
277        RemotingMessage message = (RemotingMessage)context.getMessage();
278        GraniteContext graniteContext = GraniteContext.getCurrentInstance();
279        
280        Object[] originArgs = (Object[])message.getBody();
281        IInvocationCall call = (IInvocationCall)originArgs[originArgs.length-1];
282        
283        String operation = message.getOperation();
284        String componentName = "invokeComponent".equals(operation) ? (String)originArgs[0] : null;
285        String componentClassName = "invokeComponent".equals(operation) ? (String)originArgs[1] : null;
286        Class<?> componentClass = null;
287        try {
288                if (componentClassName != null)
289                        componentClass = TypeUtil.forName(componentClassName);
290        }
291        catch (ClassNotFoundException e) {
292                throw new ServiceException("Component class not found " + componentClassName, e);
293        }
294        
295        graniteContext.getRequestMap().put(TideServiceInvoker.class.getName(), this);
296        
297        if (componentName != null || componentClass != null) {
298                Converters converters = graniteContext.getGraniteConfig().getConverters();
299                
300                Set<Class<?>> componentClasses = tideContext.findComponentClasses(componentName, componentClass);
301                for (Class<?> cClass : componentClasses) {
302                        try {
303                                Method m = cClass.getMethod(context.getMethod().getName(), context.getMethod().getParameterTypes());
304                                for (int i = 0; i < m.getGenericParameterTypes().length; i++)
305                                        context.getParameters()[i] = converters.convert(context.getParameters()[i], m.getGenericParameterTypes()[i]);
306                                
307                                break;
308                        }
309                        catch (NoSuchMethodException e) {
310                        }
311                }
312                
313                for (Class<?> cClass : componentClasses) {
314                        DataEnabled dataEnabled = cClass.getAnnotation(DataEnabled.class);
315                        if (dataEnabled != null && !dataEnabled.useInterceptor()) {
316                                GraniteContext.getCurrentInstance().getRequestMap().put(DATAENABLED_HANDLED, true);
317                                DataContext.init(dataEnabled.topic(), dataEnabled.params(), dataEnabled.publish());
318                                prepareDataObserver(dataEnabled);
319                                break;
320                        }
321                }
322        }
323        
324        Throwable error = null;
325        try {
326                tideContext.prepareCall(context, call, componentName, componentClass);
327        }
328        catch (ServiceException e) {
329                error = e;
330        }
331        catch (Throwable e) {
332                if (e instanceof InvocationTargetException)
333                        error = ((InvocationTargetException)e).getTargetException();
334                else
335                        error = e;
336        } 
337        finally {
338                if (error != null)
339                        throw factory.getServiceExceptionHandler().handleInvocationException(context, error);
340        }
341    }
342        
343    protected void prepareDataObserver(DataEnabled dataEnabled) {
344                DataContext.observe();
345    }
346
347
348    @Override
349    protected Object afterInvocation(ServiceInvocationContext context, Object result) {
350        Object res = null;
351        
352        String componentName = null;
353        Class<?> componentClass = null;
354        try {
355                Object[] originArgs = (Object[])context.getMessage().getBody();
356                String operation = ((RemotingMessage)context.getMessage()).getOperation();
357                componentName = "invokeComponent".equals(operation) ? (String)originArgs[0] : null;
358                String componentClassName = "invokeComponent".equals(operation) ? (String)originArgs[1] : null;
359                try {
360                        if (componentClassName != null)
361                                componentClass = TypeUtil.forName(componentClassName);
362                }
363                catch (ClassNotFoundException e) {
364                        throw new ServiceException("Component class not found " + componentClassName, e);
365                }
366        }
367        finally {
368            Throwable error = null;
369            try {
370                res = tideContext.postCall(context, result, componentName, componentClass);
371            }
372            catch (ServiceException e) {
373                error = e;
374            }
375            catch (Throwable e) {
376                if (e instanceof InvocationTargetException)
377                        error = ((InvocationTargetException)e).getTargetException();
378                else
379                        error = e;
380            }
381            finally {
382                if (error != null)
383                        throw factory.getServiceExceptionHandler().handleInvocationException(context, error);
384            }
385        }
386        
387        DataMergeContext.remove();
388        
389        // DataContext has been setup by beforeInvocation
390                if (GraniteContext.getCurrentInstance().getRequestMap().get(DATAENABLED_HANDLED) != null)
391                publishDataUpdates();
392        
393                DataContext.remove();
394        
395        return res;
396    }
397    
398    protected void publishDataUpdates() {
399                DataContext.publish(PublishMode.ON_SUCCESS);
400    }
401
402    
403    @Override
404    protected void afterInvocationError(ServiceInvocationContext context, Throwable error) {
405        String componentName = null;
406        Class<?> componentClass = null;
407        try {
408                Object[] originArgs = (Object[])context.getMessage().getBody();
409                String operation = ((RemotingMessage)context.getMessage()).getOperation();
410                componentName = "invokeComponent".equals(operation) ? (String)originArgs[0] : null;
411                String componentClassName = "invokeComponent".equals(operation) ? (String)originArgs[1] : null;
412                try {
413                        if (componentClassName != null)
414                                componentClass = TypeUtil.forName(componentClassName);
415                }
416                catch (ClassNotFoundException e) {
417                        throw new ServiceException("Component class not found " + componentClassName, e);
418                }
419        }
420        finally {
421                tideContext.postCallFault(context, error, componentName, componentClass);
422        }
423        
424        DataMergeContext.remove();
425                DataContext.remove();
426    }
427}