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.seam;
022    
023    import java.lang.reflect.Field;
024    import java.lang.reflect.Method;
025    import java.lang.reflect.Type;
026    import java.util.ArrayList;
027    import java.util.HashMap;
028    import java.util.HashSet;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Set;
032    
033    import javax.persistence.Entity;
034    import javax.servlet.http.HttpSession;
035    
036    import org.granite.config.GraniteConfig;
037    import org.granite.context.GraniteContext;
038    import org.granite.messaging.amf.io.util.ClassGetter;
039    import org.granite.messaging.service.ServiceException;
040    import org.granite.messaging.service.ServiceInvocationContext;
041    import org.granite.messaging.webapp.HttpGraniteContext;
042    import org.granite.tide.IInvocationCall;
043    import org.granite.tide.IInvocationResult;
044    import org.granite.tide.TidePersistenceManager;
045    import org.granite.tide.TideServiceContext;
046    import org.granite.tide.TideStatusMessages;
047    import org.granite.tide.annotations.BypassTideMerge;
048    import org.granite.tide.async.AsyncPublisher;
049    import org.granite.tide.data.DataContext;
050    import org.granite.tide.data.DataMergeContext;
051    import org.granite.tide.data.DataUpdatePostprocessor;
052    import org.granite.tide.invocation.ContextEvent;
053    import org.granite.tide.invocation.ContextResult;
054    import org.granite.tide.invocation.ContextUpdate;
055    import org.granite.tide.invocation.InvocationCall;
056    import org.granite.tide.invocation.InvocationResult;
057    import org.granite.tide.seam.async.AsyncContext;
058    import org.granite.tide.seam.lazy.SeamInitializer;
059    import org.jboss.seam.Component;
060    import org.jboss.seam.ScopeType;
061    import org.jboss.seam.annotations.Destroy;
062    import org.jboss.seam.annotations.Factory;
063    import org.jboss.seam.annotations.Logger;
064    import org.jboss.seam.annotations.Observer;
065    import org.jboss.seam.annotations.datamodel.DataModelSelection;
066    import org.jboss.seam.annotations.security.Restrict;
067    import org.jboss.seam.contexts.Context;
068    import org.jboss.seam.contexts.Contexts;
069    import org.jboss.seam.core.Conversation;
070    import org.jboss.seam.core.Init.FactoryExpression;
071    import org.jboss.seam.core.Init.FactoryMethod;
072    import org.jboss.seam.framework.Home;
073    import org.jboss.seam.log.Log;
074    import org.jboss.seam.security.Identity;
075    import org.jboss.seam.util.Reflections;
076    
077    
078    /**
079     * @author William DRAI
080     */
081    public abstract class AbstractSeamServiceContext extends TideServiceContext {
082    
083        private static final long serialVersionUID = 1L;
084        
085        public static final String COMPONENT_NAME = "org.granite.tide.seam.serviceContext";
086        
087        protected @Logger Log log;
088        
089        private UserEvents userEvents;
090        private boolean isAsynchronousContext = true;
091        
092        
093        private static final String RESULTS_EVAL_ATTRIBUTE = AbstractSeamServiceContext.class.getName() + "_resultsEval";
094        private static final String RESULTS_EVAL_UNSPECIFIED_ATTRIBUTE = AbstractSeamServiceContext.class.getName() + "_resultsEval_unspecified";
095        
096        
097        public AbstractSeamServiceContext() throws ServiceException {
098            super();
099        }
100        
101        /**
102         * Determines the current sessionId for web invocations
103         */
104        @Override
105        public void initCall() {
106            super.initCall();
107            
108            if (userEvents != null)
109                return;
110            
111            if (getSessionId() != null)
112                userEvents = TideUserEvents.instance().getUserEvents(getSessionId());
113            else {
114                GraniteContext graniteContext = GraniteContext.getCurrentInstance();
115                if (graniteContext instanceof HttpGraniteContext) {
116                    HttpSession session = ((HttpGraniteContext)graniteContext).getSession(false);
117                    if (session != null)
118                        setSessionId(session.getId());
119                    isAsynchronousContext = false;
120                }
121            }
122        }
123        
124        /**
125         * Initialize current sessionId and event listeners for this context
126         * 
127         * @param sessionId current sessionId
128         */
129        @Override
130        public void setSessionId(String sessionId) {
131            super.setSessionId(sessionId);
132            userEvents = TideUserEvents.instance().getUserEvents(sessionId);
133        }
134        
135        /**
136         * Clear current session from user events registry
137         */
138        @Destroy
139        public void endSession() {
140            if (!isAsynchronousContext && getSessionId() != null)
141                TideUserEvents.instance().unregisterSession(getSessionId());
142        }
143        
144        
145        private Map<ContextResult, Boolean> getResultsEval(ScopeType scopeType) {
146            Context context = Contexts.getEventContext();
147            String att = RESULTS_EVAL_UNSPECIFIED_ATTRIBUTE;
148            if (scopeType == ScopeType.STATELESS)
149                    att = RESULTS_EVAL_ATTRIBUTE;
150            else if (scopeType != ScopeType.UNSPECIFIED) {
151                    context = scopeType.getContext();
152                    att = RESULTS_EVAL_ATTRIBUTE;
153            }
154            
155            @SuppressWarnings("unchecked")
156            Map<ContextResult, Boolean> resultsEval = (Map<ContextResult, Boolean>)context.get(att);
157            if (resultsEval == null) {
158                    resultsEval = new HashMap<ContextResult, Boolean>();
159                    context.set(att, resultsEval);
160            }
161            return resultsEval;
162        }
163        
164        
165        /**
166         * Constructs an asynchronous context object
167         * @return current context
168         */
169        public AsyncContext getAsyncContext() {
170            List<ContextResult> resultsEval = new ArrayList<ContextResult>();
171            for (ScopeType evalScopeType : EVAL_SCOPE_TYPES)
172                    resultsEval.addAll(getResultsEval(evalScopeType).keySet());
173            
174            return new AsyncContext(getSessionId(), resultsEval);
175        }
176        
177        /**
178         * Restores an asynchronous context
179         * @param asyncContext saved context
180         */
181        public void setAsyncContext(AsyncContext asyncContext) {
182            AsyncPublisher asyncPublisher = getAsyncPublisher();
183            if (asyncPublisher != null)
184                asyncPublisher.initThread();
185            
186            Contexts.getSessionContext().set("org.jboss.seam.security.identity", asyncContext.getIdentity());
187            setSessionId(asyncContext.getSessionId());
188            for (ContextResult resultEval : asyncContext.getResults()) {
189                    if (resultEval instanceof ScopedContextResult)
190                            getResultsEval(((ScopedContextResult)resultEval).getScope()).put(resultEval, Boolean.FALSE);
191                    else
192                            getResultsEval(ScopeType.UNSPECIFIED).put(resultEval, Boolean.FALSE);
193            }
194        }
195        
196        
197        /**
198         * Retrieve current messages
199         * 
200         * @return list of messages
201         */
202        protected abstract TideStatusMessages getTideMessages();
203        
204        protected abstract void initTideMessages();
205        
206        protected abstract void clearTideMessages();
207        
208        
209        /**
210         * Implementation of component lookup for Seam service
211         * 
212         * @param componentName component name
213         */
214        @Override
215        public Object findComponent(String componentName, Class<?> componentClass) {
216            Component component = TideInit.lookupComponent(componentName);
217            if (component == null)
218                return null;
219            return Component.getInstance(component.getName());
220        }
221        
222        /**
223         * Implementation of component lookup for Seam service
224         * 
225         * @param componentName component name
226         */
227        @Override
228        public Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass) {
229            Component component = TideInit.lookupComponent(componentName);
230            if (component == null)
231                return null;
232            
233            return componentClasses(component);
234        }
235        
236        private Set<Class<?>> componentClasses(Object component) {
237            if (component instanceof Component) {
238                    Set<Class<?>> classes = new HashSet<Class<?>>();
239                    for (Class<?> i : ((Component)component).getBusinessInterfaces())
240                            classes.add(i);
241                    classes.add(((Component)component).getBeanClass());
242                    return classes;
243            }
244            
245            Set<Class<?>> classes = new HashSet<Class<?>>(1);
246            classes.add(component.getClass());
247            return classes;
248        }
249        
250        
251        @Observer("org.jboss.seam.beginConversation")
252        public void observeBeginConversation() {
253            Contexts.getEventContext().set("org.granite.tide.conversation.wasCreated", true);
254        }
255        
256        
257        /**
258         * Add an event in the current context
259         * 
260         * @param type event type
261         * @param params event parameters
262         */
263        public void raiseEvent(String type, Object... params) {
264            // Add the event to the current invocation
265            TideInvocation tideInvocation = TideInvocation.get();
266            if (tideInvocation == null)
267                return;
268            
269            if (userEvents != null) {
270                // Avoid stupid infinite loop when creating locale selector as the log needs the current locale...
271                if (!type.endsWith("org.jboss.seam.international.localeSelector"))
272                    log.debug("Intercept event %s", type);
273                String sessionId = getSessionId();
274                if (sessionId != null && userEvents.hasEventType(type))
275                    tideInvocation.addEvent(new ContextEvent(type, params));
276            }
277            else if (Contexts.getSessionContext().isSet("org.granite.seam.login")) {
278                // Force send of all events raised during login
279                tideInvocation.addEvent(new ContextEvent(type, params));
280            }
281        }
282        
283        
284        /**
285         * Factory for Seam async publisher
286         * 
287         * @return servlet context of the current application
288         */
289        @Override
290        protected AsyncPublisher getAsyncPublisher() {
291            return (AsyncPublisher)Component.getInstance("org.granite.tide.seam.async.publisher");
292        }
293        
294        
295        /**
296         * Synchronizes server context with data provided by the client
297         * 
298         * @param context invocation context
299         * @param c client call
300         * @param componentName name of the component which will be invoked
301         */
302        @Override
303        public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) {
304            InvocationCall call = (InvocationCall)c;
305            List<String> listeners = call.getListeners();
306            List<ContextUpdate> updates = call.getUpdates();
307            Object[] results = call.getResults();
308            
309            if (Contexts.isEventContextActive() && Contexts.isSessionContextActive() && 
310                            (Contexts.getSessionContext().isSet("org.granite.tide.isFirstCall") || Contexts.getSessionContext().isSet("org.granite.seam.login"))) {
311                // Login tried : force evaluation of existing session context
312                for (Map.Entry<ContextResult, Boolean> me : getResultsEval(ScopeType.SESSION).entrySet()) {
313                    if (me.getKey().getExpression() == null && Contexts.getSessionContext().isSet(me.getKey().getComponentName()))
314                        me.setValue(Boolean.TRUE);
315                }
316                Contexts.getSessionContext().remove("org.granite.seam.login");
317                Contexts.getSessionContext().remove("org.granite.tide.isFirstCall");
318                
319                // Force quiet login
320                if (Identity.instance().isLoggedIn() && Contexts.isEventContextActive())
321                   Contexts.getEventContext().set("org.jboss.seam.security.silentLogin", true);
322            }
323            if (Contexts.isEventContextActive() && Contexts.isConversationContextActive() && 
324                            Contexts.getConversationContext().isSet("org.granite.tide.isFirstConversationCall")) {
325                // Join conversation : force evaluation of existing conversation context
326                for (Map.Entry<ContextResult, Boolean> me : getResultsEval(ScopeType.CONVERSATION).entrySet()) {
327                    if (me.getKey().getExpression() == null && Contexts.getConversationContext().isSet(me.getKey().getComponentName()))
328                        me.setValue(Boolean.TRUE);
329                }
330                Contexts.getConversationContext().remove("org.granite.tide.isFirstConversationCall");
331            }
332            
333            String sessionId = getSessionId();
334            if (sessionId != null && listeners != null) {
335                // Registers new event listeners
336                for (String listener : listeners)
337                    TideUserEvents.instance().registerEventType(sessionId, listener);
338                
339                if (userEvents == null)
340                    userEvents = TideUserEvents.instance().getUserEvents(getSessionId());
341            }
342            
343            if (results != null) {
344                    Map<ContextResult, Boolean> resultsEval = getResultsEval(ScopeType.UNSPECIFIED);
345                for (Object result : results) {
346                    ContextResult cr = (ContextResult)result;
347                    resultsEval.put(new ScopedContextResult(cr.getComponentName(), cr.getExpression(), ScopeType.UNSPECIFIED, null), Boolean.TRUE);
348                    
349        //            if (!factories.containsKey(cr.getComponentName())) {
350        //                FactoryExpression expr = Init.instance().getFactoryValueExpression(cr.getComponentName());
351        //                if (expr != null) {
352        //                    String vexpr = expr.getValueBinding().getExpressionString();
353        //                    vexpr = vexpr.substring(2, vexpr.indexOf('.', 2));
354        //                    factories.put(cr.getComponentName(), vexpr);
355        //                    
356        //                    resultsEval.put(new ContextResult(cr.getComponentName(), null), Boolean.TRUE);
357        //                }
358        //            }
359                }
360            }
361            
362            boolean instrumented = false;
363            Component component = null;
364            if (componentName != null) {
365                component = TideInit.lookupComponent(componentName);
366                if (component.isInterceptionEnabled())
367                    instrumented = true;
368                
369                // Forces evaluation of all results if they are related to the called component
370                for (Map.Entry<ContextResult, Boolean> me : getResultsEval(component.getScope()).entrySet()) {
371                    if (me.getKey().getComponentName().equals(componentName))
372    //                    || componentName.equals(factories.get(me.getKey().getComponentName())))
373                        me.setValue(Boolean.TRUE);
374                }
375            }
376            
377            initTideMessages();
378            
379            SeamInitializer.instance().restoreLoadedEntities();
380            
381            if (Conversation.instance().isLongRunning()) {
382                // Merge call arguments with current conversation context
383                Object[] args = context.getParameters();
384                if (args != null) {
385                        for (int i = 0; i < args.length; i++) {
386                            Object value = mergeExternal(args[i], null);
387                            args[i] = value;
388                        }
389                }
390            }
391            
392            // Initialize an empty data context
393            DataContext.init();
394            
395                    DataUpdatePostprocessor dataUpdatePostprocessor = (DataUpdatePostprocessor)Component.getInstance("org.granite.tide.seam.data.dataUpdatePreprocessor", true);
396                    if (dataUpdatePostprocessor != null)
397                            DataContext.get().setDataUpdatePostprocessor(dataUpdatePostprocessor);
398            
399            // Initialize invocation context with received changes to apply to the server context and results to return 
400            TideInvocation tideInvocation = TideInvocation.init();
401            tideInvocation.update(updates);
402            
403            if (!instrumented) {
404                // If no interception enabled, force the update of the context for the current component
405                // In other cases it will be done by the interceptor 
406                restoreContext(updates, component, null);
407                tideInvocation.updated();
408            }
409        }
410    
411        /**
412         * Builds the result object for the invocation
413         * 
414         * @param context invocation context
415         * @param result result of the method invocation
416         * @param componentName name of the invoked component
417         * @return result object
418         */
419        @Override
420        public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) {
421            TideInvocation tideInvocation = TideInvocation.get();
422            List<ContextUpdate> results = null;
423            int scope = 3;
424            boolean restrict = false;
425            
426            Component component = null;
427            if (componentName != null) {
428                // Determines scope of component
429                component = TideInit.lookupComponent(componentName);
430                if (Contexts.isMethodContextActive() && Contexts.getMethodContext().isSet(component.getName()))
431                    scope = 3;
432                else if (Contexts.isEventContextActive() && Contexts.getEventContext().isSet(component.getName()))
433                    scope = 3;
434                else if (Contexts.isPageContextActive() && Contexts.getPageContext().isSet(component.getName()))
435                    scope = 3;
436                else if (Contexts.isConversationContextActive() && Contexts.getConversationContext().isSet(component.getName()))
437                    scope = 2;
438                
439                restrict = component.beanClassHasAnnotation(Restrict.class);
440            }
441            
442            if (!tideInvocation.isEvaluated()) {
443                // Do evaluation now if the interceptor has not been called
444                results = evaluateResults(null, null, componentName == null 
445                            && !(context != null && context.getMethod() != null && "resyncContext".equals(context.getMethod().getName())));
446            }
447            else
448                results = tideInvocation.getResults();
449            
450            // Retrieve data updates made during the call
451            DataContext dataContext = DataContext.get();
452                    Object[][] updates = dataContext != null ? dataContext.getUpdates() : null;
453                    
454                    // Build the invocation result
455            InvocationResult ires = new InvocationResult(result, results);
456            ires.setScope(scope);
457            ires.setRestrict(restrict);
458            if (component != null) {
459                    if (component.beanClassHasAnnotation(BypassTideMerge.class) || component.businessInterfaceHasAnnotation(BypassTideMerge.class))
460                            ires.setMerge(false);
461                    else if (context != null) {
462                            try {
463                                    Method m = component.getBeanClass().getMethod(context.getMethod().getName(), context.getMethod().getParameterTypes());
464                                    if (m.isAnnotationPresent(BypassTideMerge.class))
465                                            ires.setMerge(false);
466                            }
467                            catch (NoSuchMethodException e) {
468                                    log.warn("Could not find bean method", e);
469                            }
470                            
471                            for (Class<?> beanInterface : component.getBusinessInterfaces()) {
472                            try {
473                                    Method m = beanInterface.getMethod(context.getMethod().getName(), context.getMethod().getParameterTypes());
474                                    if (m.isAnnotationPresent(BypassTideMerge.class)) {
475                                            ires.setMerge(false);
476                                            break;
477                                    }
478                            }
479                            catch (NoSuchMethodException e) {
480                                    log.warn("Could not find bean method", e);
481                            }
482                            }
483                    }
484            }
485            
486            if (Conversation.instance().isLongRunning()) {
487                    // Put results in merge context to keep data in extended persistence context between remote calls
488                    DataMergeContext.addResultEntity(result);
489                    for (ContextUpdate cu : results)
490                            DataMergeContext.addResultEntity(cu.getValue());
491            }
492            
493            ires.setUpdates(updates);
494            
495            // Adds events in result object
496            ires.setEvents(tideInvocation.getEvents());
497            
498            // Save current set of entities loaded in a conversation scoped component to handle case of extended PM
499            SeamInitializer.instance().saveLoadedEntities();
500            
501            // Adds context messages in result object
502            TideStatusMessages statusMessages = getTideMessages();
503            ires.setMessages(statusMessages.getMessages());
504            ires.setKeyedMessages(statusMessages.getKeyedMessages());
505            
506            clearTideMessages();
507    
508            // Clean thread
509            TideInvocation.remove();
510            
511            return ires;
512        }
513    
514        /**
515         * Intercepts a fault on the invocation
516         * 
517         * @param context invocation context
518         * @param t exception thrown
519         * @param componentName name of the invoked component
520         */
521        @Override
522        public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
523            clearTideMessages();
524            
525            // Clean thread: very important to avoid phantom evaluations after exceptions
526            TideInvocation.remove();
527        }
528        
529        
530        public void addResultEval(ScopedContextResult result) {
531            getResultsEval(result.getScope()).put(result, Boolean.TRUE);
532        }
533        
534        
535        /**
536         * Evaluate updates in current server context
537         * 
538         * @param updates list of updates
539         * @param component the target component
540         * @param target the target instance
541         */
542        public void restoreContext(List<ContextUpdate> updates, Component component, Object target) {
543            if (updates == null)
544                return;
545            
546            GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
547            
548            // Restore context
549            for (ContextUpdate update : updates) {
550                
551                log.debug("Before invocation: evaluating expression #0.#1", update.getComponentName(), update.getExpression());
552                
553                Component sourceComponent = TideInit.lookupComponent(update.getComponentName());
554                String sourceComponentName = sourceComponent != null ? sourceComponent.getName() : update.getComponentName();
555                
556                Object previous = null;
557                if (update.getExpression() != null) {
558                    // Set component property
559                    // Ignore expression on external components if target component is not interception enabled 
560                    // to avoid issues with bijection, persistence contexts and transactions
561                    if (target != null || (component != null && component.getName().equals(sourceComponentName))) {
562                        // Get current values in Seam context
563                        String[] path = update.getExpression().split("\\.");
564                        Object instance = component != null && component.getName().equals(sourceComponentName) && target != null 
565                            ? target 
566                            : Component.getInstance(sourceComponentName);
567                        boolean disabled = instance != null && sourceComponent != null && 
568                            config.isComponentTideDisabled(sourceComponentName, componentClasses(sourceComponent), instance);
569                        if (!disabled) {
570                            Object bean = instance;
571                            Object value = instance;
572                            
573                            List<Field> dmsFields = instance != null && path.length == 1 
574                                    ? org.granite.util.Reflections.getFields(instance.getClass(), DataModelSelection.class) : null;
575                            List<String> dmsFieldNames = null;
576                            if (dmsFields != null && !dmsFields.isEmpty()) {
577                                    dmsFieldNames = new ArrayList<String>(dmsFields.size());
578                                for (Field f : dmsFields)
579                                    dmsFieldNames.add(f.getName());
580                            }
581                            
582                            if (update.getValue() != null) {
583                                boolean getPrevious = true;
584                                if (update.getValue().getClass().getAnnotation(Entity.class) != null) {
585                                    org.granite.util.Entity entity = new org.granite.util.Entity(update.getValue());
586                                    if (entity.getIdentifier() == null)
587                                        getPrevious = false;
588                                }
589                                if (getPrevious) {
590                                    try {
591                                        for (int i = 0; i < path.length; i++) {
592                                            if (value == null)
593                                                break;
594                                            if (i == 0 && dmsFieldNames != null && dmsFieldNames.contains(path[0])) {
595                                                    Field field = org.granite.util.Reflections.getField(value.getClass(), path[i]);
596                                                    field.setAccessible(true);
597                                                    value = Reflections.get(field, value);
598                                                if (i < path.length-1)
599                                                    bean = value;
600                                            }
601                                            else {
602                                                // Use modified Reflections for getter because of a bug in Seam 2.0.0
603                                                Method getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
604                                                value = Reflections.invoke(getter, value);
605                                                if (i < path.length-1)
606                                                    bean = value;
607                                            }
608                                        }
609                                    }
610                                    catch (IllegalArgumentException e) {
611                                        // No getter found to retrieve current value
612                                        log.warn("Partial merge only: " + e.getMessage());
613                                        value = null;
614                                    }
615                                        catch (Exception e) {
616                                            throw new ServiceException("Could not get property: " + update.toString(), e);
617                                        }
618                                    previous = value;
619                                }
620                            }
621                            
622                            // Set new value
623                            try {
624                                    if (bean != null && path.length == 1 && dmsFieldNames != null && dmsFieldNames.contains(path[0])) {
625                                            Field field = org.granite.util.Reflections.getField(bean.getClass(), path[0]);
626                                            field.setAccessible(true);
627                                        value = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters().convert(update.getValue(), field.getType());
628                                        // Merge entities into current persistent context if needed
629                                        value = mergeExternal(value, previous);
630                                            Reflections.set(field, bean, value);
631                                    }
632                                    else if (bean != null) {
633                                        Method setter = Reflections.getSetterMethod(bean.getClass(), path[path.length-1]);
634                                        Type type = setter.getParameterTypes()[0];
635                                            if (bean instanceof Home<?, ?> && "id".equals(path[path.length-1])) {
636                                                    // Special (ugly ?) handling for Home object to try to guess id type (setId is of type Object)
637                                                    try {
638                                                            Class<?> entityClass = ((Home<?, ?>)bean).getEntityClass();
639                                                            org.granite.util.Entity entity = new org.granite.util.Entity(entityClass);
640                                                            type = entity.getIdentifierType();
641                                                    }
642                                                    catch (Exception e) {
643                                                            // Ignore
644                                                    }
645                                            }
646                                        value = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters().convert(update.getValue(), type);
647                                        // Merge entities into current persistent context if needed
648                                        value = mergeExternal(value, previous);
649                                        Reflections.invoke(setter, bean, value);
650                                    }
651                                }
652                                catch (Exception e) {
653                                    throw new ServiceException("Could not restore property: " + update.toString(), e);
654                                }
655                        }
656                    }
657                }
658                else {
659                    // Set context variable
660                    if (sourceComponent != null) {
661                        ScopeType scope = sourceComponent.getScope();
662                        if (!((update.getScope() == 2 && (scope == ScopeType.EVENT 
663                                    || scope == ScopeType.STATELESS 
664                                    || scope == ScopeType.CONVERSATION
665                                    || scope == ScopeType.BUSINESS_PROCESS
666                                    || scope == ScopeType.METHOD
667                                    || scope == ScopeType.PAGE))
668                            || (update.getScope() == 1 && (scope == ScopeType.EVENT
669                                    || scope == ScopeType.STATELESS 
670                                    || scope == ScopeType.SESSION
671                                    || scope == ScopeType.METHOD
672                                    || scope == ScopeType.PAGE))
673                            || (update.getScope() == 3 && (scope == ScopeType.EVENT
674                                            || scope == ScopeType.STATELESS
675                                            || scope == ScopeType.METHOD
676                                            || scope == ScopeType.PAGE)))) {
677                            scope = ScopeType.EVENT;
678                        }
679                        
680                        previous = scope.getContext().get(sourceComponentName);
681                        
682                        boolean disabled = previous != null && config.isComponentTideDisabled(sourceComponentName, componentClasses(sourceComponent), previous);
683                        if (!disabled) {
684                            Object value = mergeExternal(update.getValue(), previous);
685                            
686                            scope.getContext().set(sourceComponentName, value);
687                        }
688                    }
689                    else {
690                        Object[] prev = lookupInStatefulContexts(sourceComponentName, ScopeType.UNSPECIFIED);
691                        ScopeType scope = ScopeType.UNSPECIFIED;
692                        if (prev != null) {
693                            previous = prev[0];
694                            scope = (ScopeType)prev[1];
695                        }
696                        
697                        boolean disabled = previous != null && config.isComponentTideDisabled(sourceComponentName, 
698                                    componentClasses(previous), previous);
699                        if (!disabled) {
700                            if (scope == ScopeType.UNSPECIFIED) {
701                                scope = ScopeType.EVENT;
702                                scope.getContext().set(sourceComponentName + "_tide_unspecified_", true);
703                            }
704                            
705                            Object value = mergeExternal(update.getValue(), previous);
706                            
707                            scope.getContext().set(sourceComponentName, value);
708                        }
709                    }
710                }
711            }
712        }
713        
714        
715        private static final ScopeType[] EVAL_SCOPE_TYPES = {
716            ScopeType.UNSPECIFIED,
717            ScopeType.EVENT,
718            ScopeType.CONVERSATION,
719            ScopeType.SESSION,
720            ScopeType.BUSINESS_PROCESS,
721            ScopeType.APPLICATION
722        };
723        
724        /**
725         * Evaluate results from context
726         * 
727         * @param component the target component
728         * @param target the target instance
729         * @param nothing used by initializer to avoid interactions with context sync
730         * 
731         * @return list of updates to send back to the client
732         */
733        public List<ContextUpdate> evaluateResults(Component component, Object target, boolean nothing) {
734            
735            List<ContextUpdate> resultsMap = new ArrayList<ContextUpdate>();
736            
737            if (nothing)
738                return resultsMap;
739            
740            List<String> exprs = new ArrayList<String>();
741            GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
742            ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
743            
744            SeamInitializer.instance();
745            
746            for (ScopeType evalScopeType : EVAL_SCOPE_TYPES) {
747                    for (Map.Entry<ContextResult, Boolean> me : getResultsEval(evalScopeType).entrySet()) {
748                        if (!me.getValue())
749                            continue;
750                        
751                        ContextResult res = me.getKey();
752                        Component targetComponent = TideInit.lookupComponent(res.getComponentName());
753                        
754                        String targetComponentName = targetComponent != null ? targetComponent.getName() : res.getComponentName();
755                        
756                        if (res.getExpression() != null && component != null && targetComponent != null && !(component.getName().equals(targetComponentName))) {
757                            // Ignore results concerning other components for this time
758                            // In this case is has to be a non interception enabled component
759                            continue;
760                        }
761                        
762                        int idx = 0;
763                        boolean add = true;
764                        
765                        // Force evaluation of consecutive properties
766                        while (idx >= 0) {
767                            idx = res.getExpression() != null ? res.getExpression().indexOf(".", idx+1) : -1;
768                            
769                            String expr = (idx > 0 ? res.getExpression().substring(0, idx) : res.getExpression());
770                            String ex = expr != null ? res.getComponentName() + "." + expr : res.getComponentName();
771                            
772                            if (!exprs.contains(ex)) {
773                                log.debug("After invocation: evaluating expression #0", ex);
774                                
775                                String[] path = expr != null ? expr.split("\\.") : new String[0];
776                                
777                                try {
778                                    Object value = null;
779                                    ScopeType scopeType = res instanceof ScopedContextResult ? ((ScopedContextResult)res).getScope() : ScopeType.UNSPECIFIED;
780                                    Boolean restrict = res.getRestrict();
781                                    
782                                FactoryMethod factoryMethod = null;
783                                FactoryExpression factoryExpression = null;
784                                if (targetComponent == null) {
785                                    factoryExpression = TideInit.lookupFactoryExpression(res.getComponentName());
786                                    if (factoryExpression == null)
787                                            factoryMethod = TideInit.lookupFactory(res.getComponentName());
788                                }
789                                
790                                if (targetComponent != null) {
791                                    if (component != null && component.getName().equals(targetComponent.getName())) {
792                                            value = target;
793                                            scopeType = targetComponent.getScope();
794                                        }
795                                    else if (ScopeType.UNSPECIFIED.equals(scopeType)) {
796                                        value = Component.getInstance(targetComponent.getName());
797                                        scopeType = targetComponent.getScope();
798                                        if (ScopeType.STATELESS.equals(scopeType))
799                                            scopeType = ScopeType.EVENT;
800                                        
801                                        if (value != null && config.isComponentTideDisabled(targetComponentName, 
802                                                    componentClasses(targetComponent), value))
803                                            add = false;
804                                    }
805                                    else {
806                                        value = Component.getInstance(targetComponent.getName(), scopeType);
807                                        
808                                        if (value != null && config.isComponentTideDisabled(targetComponentName, 
809                                                    componentClasses(value), value))
810                                            add = false;
811                                    }
812                                    
813                                    restrict = targetComponent.beanClassHasAnnotation(Restrict.class);
814                                }
815                                else if (factoryExpression != null) {
816                                    String expressionString = factoryExpression.getMethodBinding() != null 
817                                            ? factoryExpression.getMethodBinding().getExpressionString() 
818                                            : factoryExpression.getValueBinding().getExpressionString();
819                                    int iedx = expressionString.indexOf(".");
820                                    String expressionBaseName = expressionString.substring(2, iedx);
821                                    
822                                    if (ScopeType.UNSPECIFIED.equals(scopeType)) {
823                                        value = Component.getInstance(res.getComponentName());
824                                        scopeType = factoryExpression.getScope();
825                                        if (ScopeType.STATELESS.equals(scopeType))
826                                            scopeType = ScopeType.EVENT;
827                                        
828                                        if (value != null && config.isComponentTideDisabled(expressionBaseName, componentClasses(value), value))
829                                            add = false;
830                                    }
831                                    else {
832                                        value = Component.getInstance(res.getComponentName(), scopeType);
833                                        
834                                        if (value != null && config.isComponentTideDisabled(expressionBaseName, componentClasses(value), value))
835                                            add = false;
836                                    }
837                                    
838                                    Component factoryComponent = TideInit.lookupComponent(expressionBaseName);
839                                    restrict = factoryComponent != null ? factoryComponent.beanClassHasAnnotation(Restrict.class) : false;
840                                }
841                                else if (factoryMethod != null) {
842                                    if (ScopeType.UNSPECIFIED.equals(scopeType)) {
843                                        value = Component.getInstance(factoryMethod.getMethod().getAnnotation(Factory.class).value());
844                                        scopeType = factoryMethod.getScope();
845                                        if (ScopeType.STATELESS.equals(scopeType))
846                                            scopeType = ScopeType.EVENT;
847                                        
848                                        if (value != null && config.isComponentTideDisabled(factoryMethod.getComponent().getName(), 
849                                                    componentClasses(factoryMethod.getComponent()), value))
850                                            add = false;
851                                    }
852                                    else {
853                                        value = Component.getInstance(res.getComponentName(), scopeType);
854                                        
855                                        if (value != null && config.isComponentTideDisabled(factoryMethod.getComponent().getName(), 
856                                                    componentClasses(value), value))
857                                            add = false;
858                                    }
859    
860                                    restrict = factoryMethod.getComponent().beanClassHasAnnotation(Restrict.class);
861                                }
862                                else {
863                                    Object[] val = lookupInStatefulContexts(res.getComponentName(), scopeType);
864                                    if (val != null) {
865                                        value = val[0];
866                                        scopeType = (ScopeType)val[1];
867                                        
868                                        if (value != null && config.isComponentTideDisabled(res.getComponentName(), 
869                                                    componentClasses(value), value))
870                                            add = false;
871                                    }
872                                }
873                                    
874                                if (add) {
875                                    Object v0 = null;
876                                    String propName = null;
877                                    for (int i = 0; i < path.length; i++) {
878                                        if (value == null)
879                                            break;
880                                        // Use modified Reflections for getter because of a bug in Seam 2.0.0
881                                        v0 = value;
882                                        propName = path[i];
883                                        Method getter = null;
884                                        try {
885                                            getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
886                                        }
887                                        catch (IllegalArgumentException e) {
888                                            // GDS-566
889                                        }
890                                        if (getter != null)
891                                            value = Reflections.invoke(getter, value);
892                                    }
893                                    
894                                            getResultsEval(scopeType).put(res, false);
895                                            
896                                        if (value instanceof TideDataModel) {
897                                            // Unwrap value
898                                            value = ((TideDataModel)value).getWrappedData();
899                                        }
900                                        else if (value != null) {
901                                            if (classGetter != null) {
902                                                classGetter.initialize(v0, propName, value);
903                                                if (res.getExpression() != null) {
904                                                    String[] fullPath = res.getExpression().split("\\.");
905                                                    Object v = value;
906                                                    for (int i = path.length; i < fullPath.length; i++) {
907                                                        // Use modified Reflections for getter because of a bug in Seam 2.0.0
908                                                        Method getter = org.granite.util.Reflections.getGetterMethod(v.getClass(), fullPath[i]);
909                                                        v0 = v;
910                                                        v = Reflections.invoke(getter, v);
911    //                                                  if (v == null)
912    //                                                      break;
913                                                        classGetter.initialize(v0, fullPath[i], v);
914                                                        if (v == null)
915                                                            break;
916                                                    }
917                                                }
918                                            }
919                                        }
920                                    
921                                        int scope = (scopeType == ScopeType.CONVERSATION ? 2 : (scopeType == ScopeType.SESSION ? 1 : 3));
922                                        
923                                        resultsMap.add(new ContextUpdate(res.getComponentName(), expr, value, scope, Boolean.TRUE.equals(restrict)));
924                                        add = false;
925                                    }
926                                    
927                                    exprs.add(ex);
928                                }
929                                catch (Exception e) {
930                                    throw new ServiceException("Could not evaluate result expression: " + ex, e);
931                                }
932                            }
933                        }
934                        
935                        me.setValue(Boolean.FALSE);
936                    }
937            }
938            
939            return resultsMap;
940        }
941        
942        
943        /**
944         * Implementations of intercepted asynchronous calls
945         * Send asynchronous event to client
946         * @param asyncContext current context (session id)
947         * @param targetComponentName target component name
948         * @param methodName method name
949         * @param paramTypes method argument types
950         * @param params argument values
951         * @return result
952         */
953        public Object invokeAsynchronous(AsyncContext asyncContext, String targetComponentName, Class<?> targetComponentClass, String methodName, Class<?>[] paramTypes, Object[] params) {
954            setAsyncContext(asyncContext);
955            
956            // Just another ugly hack: the Seam interceptor has set this variable and we don't want it
957            Contexts.getEventContext().remove("org.jboss.seam.async.AsynchronousIntercepter.REENTRANT");
958            
959            Component component = TideInit.lookupComponent(targetComponentName);
960            
961            // Forces evaluation of all results if they are related to the called component
962            for (Map.Entry<ContextResult, Boolean> me : getResultsEval(component.getScope()).entrySet()) {
963                if (me.getKey().getComponentName().equals(targetComponentName))
964                    me.setValue(Boolean.TRUE);
965            }
966            
967            Object target = Component.getInstance(targetComponentName);
968            
969            Method method;
970            try {
971                method = target.getClass().getMethod(methodName, paramTypes);
972            }
973            catch (NoSuchMethodException nsme) {
974               throw new IllegalStateException(nsme);
975            }
976            
977            Object result = Reflections.invokeAndWrap(method, target, params);
978            
979            sendEvent(targetComponentName, targetComponentClass);
980            
981            return result;
982        }
983        
984    
985        @Override
986        protected TidePersistenceManager getTidePersistenceManager(boolean create) {
987            return SeamInitializer.instance().getTidePersistenceManager(); 
988        }
989        
990        
991        /**
992         * Search for a named attribute in all contexts, in the
993         * following order: method, event, page, conversation,
994         * session, business process, application.
995         * 
996         * @return the first component found, or null
997         */
998        public static Object[] lookupInStatefulContexts(String name, ScopeType scope) {
999            if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.METHOD.equals(scope)) && Contexts.isMethodContextActive()) {
1000                Object result = Contexts.getMethodContext().get(name);
1001                if (result != null)
1002                    return new Object[] { result, Contexts.getMethodContext().getType() };
1003            }
1004            
1005            if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.EVENT.equals(scope)) && Contexts.isEventContextActive()) {
1006                Object result = Contexts.getEventContext().get(name);
1007                if (result != null)
1008                    return new Object[] { result, Contexts.getEventContext().getType() };
1009            }
1010            
1011            if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.PAGE.equals(scope)) && Contexts.isPageContextActive()) {
1012                Object result = Contexts.getPageContext().get(name);
1013                if (result != null)
1014                    return new Object[] { result, Contexts.getPageContext().getType() };
1015            }
1016            
1017            if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.CONVERSATION.equals(scope)) && Contexts.isConversationContextActive()) {
1018                Object result = Contexts.getConversationContext().get(name);
1019                if (result != null)
1020                    return new Object[] { result, Contexts.getConversationContext().getType() };
1021            }
1022            
1023            if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.SESSION.equals(scope)) && Contexts.isSessionContextActive()) {
1024                Object result = Contexts.getSessionContext().get(name);
1025                if (result != null)
1026                    return new Object[] { result, Contexts.getSessionContext().getType() };
1027            }
1028            
1029            if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.BUSINESS_PROCESS.equals(scope)) && Contexts.isBusinessProcessContextActive()) {
1030                Object result = Contexts.getBusinessProcessContext().get(name);
1031                if (result != null)
1032                    return new Object[] { result, Contexts.getBusinessProcessContext().getType() };
1033            }
1034            
1035            if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.APPLICATION.equals(scope)) && Contexts.isApplicationContextActive()) {
1036                Object result = Contexts.getApplicationContext().get(name);
1037                if (result != null)
1038                    return new Object[] { result, Contexts.getApplicationContext().getType() };
1039            }
1040            
1041            return ScopeType.UNSPECIFIED.equals(scope) ? null : new Object[] { null, scope };
1042        }
1043    }