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