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