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.tide;
023    
024    import java.lang.reflect.Constructor;
025    import java.lang.reflect.InvocationTargetException;
026    import java.lang.reflect.Method;
027    import java.util.Map;
028    import java.util.Set;
029    
030    import javax.servlet.http.HttpSession;
031    
032    import org.granite.config.flex.Destination;
033    import org.granite.context.GraniteContext;
034    import org.granite.logging.Logger;
035    import org.granite.messaging.amf.io.convert.Converters;
036    import org.granite.messaging.service.ServiceException;
037    import org.granite.messaging.service.ServiceFactory;
038    import org.granite.messaging.service.ServiceInvocationContext;
039    import org.granite.messaging.service.ServiceInvoker;
040    import org.granite.messaging.service.security.SecurityServiceException;
041    import org.granite.messaging.webapp.HttpGraniteContext;
042    import org.granite.tide.data.DataContext;
043    import org.granite.tide.data.DataEnabled;
044    import org.granite.tide.data.DataMergeContext;
045    import org.granite.tide.data.DataEnabled.PublishMode;
046    import org.granite.tide.validators.EntityValidator;
047    import org.granite.tide.validators.InvalidValue;
048    import org.granite.util.TypeUtil;
049    
050    import 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     */
059    public 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.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    }