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.cdi;
022    
023    import java.lang.annotation.Annotation;
024    import java.lang.reflect.Method;
025    import java.lang.reflect.Type;
026    import java.math.BigDecimal;
027    import java.math.BigInteger;
028    import java.util.ArrayList;
029    import java.util.Arrays;
030    import java.util.HashSet;
031    import java.util.List;
032    import java.util.Map;
033    import java.util.Set;
034    
035    import javax.annotation.PreDestroy;
036    import javax.enterprise.context.ContextNotActiveException;
037    import javax.enterprise.context.ConversationScoped;
038    import javax.enterprise.context.RequestScoped;
039    import javax.enterprise.context.SessionScoped;
040    import javax.enterprise.context.spi.CreationalContext;
041    import javax.enterprise.inject.Any;
042    import javax.enterprise.inject.Default;
043    import javax.enterprise.inject.IllegalProductException;
044    import javax.enterprise.inject.spi.Bean;
045    import javax.enterprise.inject.spi.BeanManager;
046    import javax.enterprise.util.AnnotationLiteral;
047    import javax.inject.Inject;
048    import javax.persistence.Entity;
049    import javax.servlet.http.HttpSession;
050    
051    import org.granite.config.GraniteConfig;
052    import org.granite.context.GraniteContext;
053    import org.granite.logging.Logger;
054    import org.granite.messaging.amf.io.util.ClassGetter;
055    import org.granite.messaging.service.ServiceException;
056    import org.granite.messaging.service.ServiceInvocationContext;
057    import org.granite.messaging.webapp.HttpGraniteContext;
058    import org.granite.tide.IInvocationCall;
059    import org.granite.tide.IInvocationResult;
060    import org.granite.tide.TidePersistenceManager;
061    import org.granite.tide.TideServiceContext;
062    import org.granite.tide.annotations.BypassTideMerge;
063    import org.granite.tide.async.AsyncPublisher;
064    import org.granite.tide.cdi.lazy.CDIInitializer;
065    import org.granite.tide.data.DataContext;
066    import org.granite.tide.data.DataUpdatePostprocessor;
067    import org.granite.tide.invocation.ContextEvent;
068    import org.granite.tide.invocation.ContextResult;
069    import org.granite.tide.invocation.ContextUpdate;
070    import org.granite.tide.invocation.InvocationCall;
071    import org.granite.tide.invocation.InvocationResult;
072    import org.granite.util.TypeUtil;
073    import org.granite.util.Reflections;
074    import org.jboss.interceptor.util.proxy.TargetInstanceProxy;
075    
076    
077    /**
078     * @author William DRAI
079     */
080    @SessionScoped
081    public class CDIServiceContext extends TideServiceContext {
082    
083        private static final long serialVersionUID = 1L;
084        
085        private static final Logger log = Logger.getLogger(CDIServiceContext.class);
086        
087        @SuppressWarnings("serial")
088        private static final AnnotationLiteral<Any> ANY_LITERAL = new AnnotationLiteral<Any>() {}; 
089        @SuppressWarnings("serial")
090        private static final AnnotationLiteral<Default> DEFAULT_LITERAL = new AnnotationLiteral<Default>() {}; 
091        
092        private @Inject BeanManager manager;
093        
094        private @Inject TideInstrumentedBeans instrumentedBeans;
095        
096        private UserEvents userEvents;
097        private @Inject TideUserEvents tideUserEvents;
098        private boolean isAsynchronousContext = true;
099        private boolean isFirstCall = false;
100        private boolean isLogin = false;
101        private @Inject CDIInitializer tideEntityInitializer;
102        
103        
104        /**
105         * Determines the current sessionId for web invocations
106         */
107        @Override
108        public void initCall() {
109            super.initCall();
110            
111            if (userEvents != null)
112                return;
113            
114            if (getSessionId() != null)
115                userEvents = tideUserEvents.getUserEvents(getSessionId());
116            else {
117                GraniteContext graniteContext = GraniteContext.getCurrentInstance();
118                if (graniteContext instanceof HttpGraniteContext) {
119                    HttpSession session = ((HttpGraniteContext)graniteContext).getSession(false);
120                    if (session != null)
121                        setSessionId(session.getId());
122                    isAsynchronousContext = false;
123                }
124            }
125        }
126        
127        /**
128         * Initialize current sessionId and event listeners for this context
129         * 
130         * @param sessionId current sessionId
131         */
132        @Override
133        public void setSessionId(String sessionId) {
134            super.setSessionId(sessionId);
135            userEvents = tideUserEvents.getUserEvents(sessionId);
136        }
137        
138        /**
139         * Clear current session from user events registry
140         */
141        @PreDestroy
142        public void endSession() {
143            if (!isAsynchronousContext && getSessionId() != null)
144                tideUserEvents.unregisterSession(getSessionId());
145        }
146        
147        public void setLogin(boolean isLogin) {
148            this.isLogin = isLogin;
149        }
150            
151        
152        @Inject
153        private ResultsEval resultsEval;
154        
155        public Map<ContextResult, Boolean> getResultsEval() {
156            return resultsEval.getResultsEval();
157        }
158        
159        
160    //    /**
161    //     * Constructs an asynchronous context object
162    //     * @return current context
163    //     */
164    //    public AsyncContext getAsyncContext() {
165    //      List<ContextResult> resultsEval = new ArrayList<ContextResult>();
166    //      for (ScopeType evalScopeType : EVAL_SCOPE_TYPES)
167    //              resultsEval.addAll(getResultsEval(evalScopeType).keySet());
168    //      
169    //        return new AsyncContext(getSessionId(), resultsEval);
170    //    }
171    //    
172    //    /**
173    //     * Restores an asynchronous context
174    //     * @param asyncContext saved context
175    //     */
176    //    public void setAsyncContext(AsyncContext asyncContext) {
177    //        AsyncPublisher asyncPublisher = getAsyncPublisher();
178    //        if (asyncPublisher != null)
179    //            asyncPublisher.initThread();
180    //        
181    //        Contexts.getSessionContext().set("org.jboss.seam.security.identity", asyncContext.getIdentity());
182    //        setSessionId(asyncContext.getSessionId());
183    //        for (ContextResult resultEval : asyncContext.getResults()) {
184    //              if (resultEval instanceof ScopedContextResult)
185    //                      getResultsEval(((ScopedContextResult)resultEval).getScope()).put(resultEval, Boolean.FALSE);
186    //              else
187    //                      getResultsEval(ScopeType.UNSPECIFIED).put(resultEval, Boolean.FALSE);
188    //        }
189    //    }
190        
191        
192        /**
193         * Implementation of component lookup for CDI service
194         * 
195         * @param componentName component name
196         */
197        @Override
198        public Object findComponent(String componentName, Class<?> componentClass) {
199            Bean<?> bean = findBean(componentName, componentClass);
200            if (bean == null)
201                    return null;
202            CreationalContext<?> cc = manager.createCreationalContext(bean);
203            return manager.getReference(bean, Object.class, cc);
204        }
205        
206        /**
207         * Implementation of component lookup for CDI service
208         * 
209         * @param componentName component name
210         */
211        @Override
212        public Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass) {
213            Bean<?> bean = findBean(componentName, componentClass);
214            return beanClasses(bean);
215        }
216        
217        private Bean<?> findBean(String componentName, Class<?> componentClass) {
218            if (componentClass != null) {
219                    Set<Bean<?>> beans = manager.getBeans(componentClass, ANY_LITERAL);
220                    // If only one match, return match
221                    if (beans.size() == 1)
222                            return beans.iterator().next();
223            }
224    
225            if (componentName != null && !("".equals(componentName))) {
226                    // If no previous match, return by name
227                    Set<Bean<?>> beans = manager.getBeans(componentName);
228                    if (!beans.isEmpty())
229                            return beans.iterator().next();
230            }
231            
232            if (componentClass != null) {
233                    // If more than one match and no named bean, return the Default one
234                    Set<Bean<?>> beans = manager.getBeans(componentClass, DEFAULT_LITERAL);
235                    if (beans.size() == 1)
236                            return beans.iterator().next();
237            }
238            
239            return null;
240        }
241        
242        private Set<Class<?>> beanClasses(Object bean) {
243            if (bean instanceof Bean<?>) {
244                    Set<Class<?>> classes = new HashSet<Class<?>>();
245                    for (Type type : ((Bean<?>)bean).getTypes()) {
246                            if (type instanceof Class<?>)
247                                    classes.add((Class<?>)type);
248                    }
249                    return classes;
250            }
251            
252            Set<Class<?>> classes = new HashSet<Class<?>>(1);
253            classes.add(bean.getClass());
254            return classes;
255        }
256        
257        
258    //    public void observeBeginConversation(@Observes org.jboss.webbeans.conversation.) {
259    //      Contexts.getEventContext().set("org.granite.tide.conversation.wasCreated", true);
260    //    }
261        
262        
263        /**
264         * Add an event in the current context
265         * 
266         * @param event the event
267         */
268        public void processEvent(Object event) {
269            // Add the event to the current invocation
270            TideInvocation tideInvocation = TideInvocation.get();
271            if (tideInvocation == null)
272                return;
273            
274            if (userEvents != null) {
275                String sessionId = getSessionId();
276                if (sessionId != null && userEvents.hasEventType(event.getClass()))
277                    tideInvocation.addEvent(new ContextEvent(event.getClass().getName(), 
278                                    new Object[] { event, null }));
279            }
280    //        else if (Contexts.getSessionContext().isSet("org.granite.seam.login")) {
281    //            // Force send of all events raised during login
282    //            tideInvocation.addEvent(new ContextEvent(type, params));
283    //        }
284        }
285        
286        
287        /**
288         * Factory for Seam async publisher
289         * 
290         * @return servlet context of the current application
291         */
292        @Override
293        protected AsyncPublisher getAsyncPublisher() {
294            return null;
295        }
296        
297        
298        @Inject
299        private ConversationState conversation;
300        
301        /**
302         * Synchronizes server context with data provided by the client
303         * 
304         * @param context invocation context
305         * @param c client call
306         * @param componentName name of the component which will be invoked
307         */
308        @Override
309        public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) {
310            InvocationCall call = (InvocationCall)c;
311            List<String> listeners = call.getListeners();
312            List<ContextUpdate> updates = call.getUpdates();
313            Object[] results = call.getResults();
314            
315            try {
316                    if (manager.getContext(RequestScoped.class).isActive() && manager.getContext(SessionScoped.class).isActive() && isFirstCall && isLogin) {
317                        // Login tried : force evaluation of existing session context
318                        for (Map.Entry<ContextResult, Boolean> me : getResultsEval().entrySet()) {
319                            if (me.getKey().getExpression() == null 
320                                            && findBean(me.getKey().getComponentName(), me.getKey().getComponentClass()).getScope().equals(SessionScoped.class))
321                                me.setValue(Boolean.TRUE);
322                        }
323                        isLogin = false;
324                        isFirstCall = false;
325                    }
326            }
327            catch (ContextNotActiveException e) {
328                    // isActive() is not enough !!
329            }
330            try {
331                    if (manager.getContext(RequestScoped.class).isActive() && manager.getContext(ConversationScoped.class).isActive() 
332                            && conversation.isFirstCall()) { 
333                        // Join conversation : force evaluation of existing conversation context
334                        for (Map.Entry<ContextResult, Boolean> me : getResultsEval().entrySet()) {
335                            if (me.getKey().getExpression() == null 
336                                            && findBean(me.getKey().getComponentName(), me.getKey().getComponentClass()).getScope().equals(ConversationScoped.class))
337                                me.setValue(Boolean.TRUE);
338                        }
339                            conversation.setFirstCall(false);
340                    }
341            }
342            catch (ContextNotActiveException e) {
343                    // isActive() is not enough !!
344            }
345            
346            String sessionId = getSessionId();
347            if (sessionId != null && listeners != null) {
348                // Registers new event listeners
349                for (String listener : listeners) {
350                    try {
351                            Class<?> listenerClass = TypeUtil.forName(listener);
352                                    tideUserEvents.registerEventType(sessionId, listenerClass);
353                    }
354                    catch (ClassNotFoundException e) {
355                            log.error("Could not register event " + listener, e);
356                    }
357                }
358                
359                if (userEvents == null)
360                    userEvents = tideUserEvents.getUserEvents(getSessionId());
361            }
362            
363            boolean instrumented = false;
364            Bean<?> bean = findBean(componentName, componentClass);
365            if (bean != null)
366                    instrumented = instrumentedBeans.getBean(bean.getBeanClass()) != null;
367            
368            if (results != null) {
369                    Map<ContextResult, Boolean> resultsEval = getResultsEval();
370                for (Object result : results) {
371                    ContextResult cr = (ContextResult)result;
372                    resultsEval.put(cr, Boolean.TRUE);
373                }
374            }
375            
376            try {
377                    tideEntityInitializer.restoreLoadedEntities();
378            }
379            catch (ContextNotActiveException e) {
380                    // Not in a conversation
381            }
382            
383            // Initialize an empty data context
384            DataContext.init();
385            
386            DataUpdatePostprocessor dataUpdatePostprocessor = (DataUpdatePostprocessor)findComponent(null, DataUpdatePostprocessor.class);
387            if (dataUpdatePostprocessor != null)
388                    DataContext.get().setDataUpdatePostprocessor(dataUpdatePostprocessor);
389            
390            TideInvocation tideInvocation = TideInvocation.init();
391            tideInvocation.update(updates);
392            
393            if (!instrumented) {
394                // If no interception enabled, force the update of the context for the current component
395                // In other cases it will be done by the interceptor 
396                restoreContext(updates, null);
397                tideInvocation.updated();
398            }
399        }
400    
401        /**
402         * Builds the result object for the invocation
403         * 
404         * @param context invocation context
405         * @param result result of the method invocation
406         * @param componentName name of the invoked component
407         * @return result object
408         */
409        @Override
410        public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) {
411            TideInvocation tideInvocation = TideInvocation.get();
412            int scope = 3;
413            boolean restrict = false;
414            
415                    List<ContextUpdate> results = null;
416            if (!tideInvocation.isEvaluated()) {
417                // Do evaluation now if the interceptor has not been called
418                results = evaluateResults(null, false);
419            }
420            else
421                results = tideInvocation.getResults();
422    
423            DataContext dataContext = DataContext.get();
424                    Object[][] updates = dataContext != null ? dataContext.getUpdates() : null;
425                    
426            Bean<?> bean = null;
427            if (componentName != null || componentClass != null) {
428                // Determines scope of component
429                bean = findBean(componentName, componentClass);
430                if (bean.getScope() == RequestScoped.class)
431                    scope = 3;
432                else if (bean.getScope() == ConversationScoped.class)
433                    scope = 2;
434                else if (bean.getScope() == SessionScoped.class)
435                    scope = 1;
436                
437                try {
438                        if (manager.getContext(RequestScoped.class).get(bean) != null)
439                            scope = 3;
440                        else if (manager.getContext(ConversationScoped.class).get(bean) != null)
441                            scope = 2;
442                        else if (manager.getContext(SessionScoped.class).get(bean) != null)
443                            scope = 1;
444                }
445                catch (ContextNotActiveException e) {
446                    scope = 3;
447                }
448            }
449            
450            InvocationResult res = new InvocationResult(result, results);
451            res.setScope(scope);
452            res.setRestrict(restrict);
453            if (bean != null) {
454                    if (bean.getBeanClass().isAnnotationPresent(BypassTideMerge.class))
455                            res.setMerge(false);
456                    else {
457                            try {
458                                    if (context != null) {
459                                            Method m = bean.getBeanClass().getMethod(context.getMethod().getName(), context.getMethod().getParameterTypes());
460                                            if (m.isAnnotationPresent(BypassTideMerge.class))
461                                                    res.setMerge(false);
462                                    }
463                            }
464                            catch (Exception e) {
465                                    log.warn("Could not find bean method", e);
466                            }
467                    }
468            }
469            
470            res.setUpdates(updates);
471            
472            // Adds events in result object
473            res.setEvents(tideInvocation.getEvents());
474            
475            try {
476                    // Save current set of entities loaded in a conversation scoped component to handle case of extended PM
477                    tideEntityInitializer.saveLoadedEntities();
478            }
479            catch (ContextNotActiveException e) {
480                    // Not in a conversation
481            }
482    
483            // Clean thread
484            TideInvocation.remove();
485            
486            return res;
487        }
488    
489        /**
490         * Intercepts a fault on the invocation
491         * 
492         * @param context invocation context
493         * @param t exception thrown
494         * @param componentName name of the invoked component
495         */
496        @Override
497        public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
498            // Clean thread: very important to avoid phantom evaluations after exceptions
499            TideInvocation.remove();
500        }
501        
502        
503        public void addResultEval(ContextResult result) {
504            getResultsEval().put(result, Boolean.TRUE);
505        }
506        
507        
508        private static Object unproxy(Object obj) {
509            // TODO: Works only with Weld !!
510            if (obj instanceof TargetInstanceProxy<?>) {
511                    try {
512                            return ((TargetInstanceProxy<?>)obj).getTargetInstance();
513                    }
514                    catch (IllegalProductException e) {
515                            return null;
516                    }
517            }
518            return obj;
519        }
520        
521        /**
522         * Evaluate updates in current server context
523         * 
524         * @param updates list of updates
525         * @param target the target instance
526         */
527        public void restoreContext(List<ContextUpdate> updates, Object target) {
528            if (updates == null)
529                return;
530            
531            try {
532                    TideInvocation.get().lock();
533                    
534                    GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
535                    
536                    // Restore context
537                    for (ContextUpdate update : updates) {
538                        
539                        log.debug("Before invocation: evaluating expression #0(#1).#2", update.getComponentName(), update.getComponentClassName(), update.getExpression());
540                        
541                        Class<?> componentClass = update.getComponentClass();
542                        Bean<?> sourceBean = findBean(update.getComponentName(), componentClass);
543                        
544                        Object previous = null;
545                        if (update.getExpression() != null) {
546                            String[] path = update.getExpression().split("\\.");
547                            Object instance = manager.getReference(sourceBean, Object.class, manager.createCreationalContext(sourceBean));
548                            boolean disabled = instance != null 
549                                    && config.isComponentTideDisabled(sourceBean.getName(), beanClasses(sourceBean), instance);
550                            if (!disabled) {
551                                    instance = unproxy(instance);
552                                    
553                                Object bean = instance;
554                                Object value = instance;
555                                
556                                if (update.getValue() != null) {
557                                    boolean getPrevious = true;
558                                    if (update.getValue().getClass().isPrimitive() || isImmutable(update.getValue())) {
559                                            getPrevious = false;
560                                    }
561                                    else if (update.getValue().getClass().getAnnotation(Entity.class) != null) {
562                                        org.granite.util.Entity entity = new org.granite.util.Entity(update.getValue());
563                                        if (entity.getIdentifier() == null)
564                                            getPrevious = false;
565                                    }
566                                    if (getPrevious) {
567                                        try {
568                                            for (int i = 0; i < path.length; i++) {
569                                                if (value == null)
570                                                    break;
571                                                // Use modified Reflections for getter because of a bug in Seam 2.0.0
572                                                Method getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
573                                                value = Reflections.invoke(getter, value);
574                                                if (i < path.length-1)
575                                                    bean = value;
576                                            }
577                                        }
578                                        catch (IllegalArgumentException e) {
579                                            // No getter found to retrieve current value
580                                            log.warn(e, "Partial merge only");
581                                            value = null;
582                                        }
583                                        catch (Exception e) {
584                                            throw new ServiceException("Could not get property: " + update.toString(), e);
585                                        }
586                                        previous = value;
587                                    }
588                                }
589                                
590                                // Set new value
591                                try {
592                                    if (bean != null) {
593                                        Method setter = Reflections.getSetterMethod(bean.getClass(), path[path.length-1]);
594                                        Type type = setter.getParameterTypes()[0];
595                                        value = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters().convert(update.getValue(), type);
596                                        // Merge entities into current persistent context if needed
597                                        value = mergeExternal(value, previous);
598                                        Reflections.invoke(setter, bean, value);
599                                    }
600                                }
601                                catch (Exception e) {
602                                    throw new ServiceException("Could not restore property: " + update.toString(), e);
603                                }
604                            }
605                        }
606                        else {
607                            previous = manager.getReference(sourceBean, Object.class, manager.createCreationalContext(sourceBean));                 
608                            boolean disabled = previous != null 
609                                    && config.isComponentTideDisabled(sourceBean.getName(), beanClasses(sourceBean), previous);
610                            if (!disabled) {
611                                    previous = unproxy(previous);
612                                    
613                                    // Merge context variable
614                                    mergeExternal(update.getValue(), previous);
615                            }
616                        }
617                    }
618            }
619            finally {
620                    TideInvocation.get().unlock();
621            }
622        }
623        
624        
625        /**
626         * Evaluate results from context
627         * 
628         * @param target the target instance
629         * @param nothing used by initializer to avoid interactions with context sync
630         * 
631         * @return list of updates to send back to the client
632         */
633        public List<ContextUpdate> evaluateResults(Object target, boolean nothing) {
634            
635            List<ContextUpdate> resultsMap = new ArrayList<ContextUpdate>();
636            
637            if (nothing)
638                return resultsMap;
639            
640            try {
641                    TideInvocation.get().lock();
642                            
643                    GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
644                    ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
645                    
646                    for (Map.Entry<ContextResult, Boolean> me : getResultsEval().entrySet()) {
647                        if (!me.getValue())
648                            continue;
649                        
650                        ContextResult res = me.getKey();
651                        
652                        Class<?> componentClass = res.getComponentClass();
653                        Bean<?> targetBean = findBean(res.getComponentName(), componentClass);
654                        if (targetBean == null) {
655                            log.warn("Target bean " + res.getComponentName() + " of class " + componentClass + " not found");
656                            continue;
657                        }
658                        String targetComponentName = targetBean.getName();
659                        
660                        boolean add = true;
661                        Class<? extends Annotation> scopeType = targetBean.getScope();
662                        Boolean restrict = res.getRestrict();
663                        
664                        Object value = res instanceof ScopedContextResult 
665                            ? ((ScopedContextResult)res).getValue() 
666                            : manager.getReference(targetBean, Object.class, manager.createCreationalContext(targetBean));
667                        
668                        if (value != null && config.isComponentTideDisabled(targetComponentName, beanClasses(targetBean), value))
669                            add = false;
670                        
671                        if (add) {
672                            getResultsEval().put(res, false);
673                            
674                            String[] path = res.getExpression() != null ? res.getExpression().split("\\.") : new String[0];
675                            
676                            value = unproxy(value);
677                            
678                            if (value != null) {
679                                    try {
680                                        for (int i = 0; i < path.length; i++) {
681                                            if (value == null)
682                                                break;
683                                            try {
684                                                    Method getter = Reflections.getGetterMethod(value.getClass(), path[i]);
685                                                    value = Reflections.invoke(getter, value);
686                                            }
687                                            catch (IllegalArgumentException e) {
688                                                    // GDS-566
689                                                    add = false;
690                                            }                                   
691                                        }
692                                    }
693                                    catch (Exception e) {
694                                            throw new ServiceException("Could not evaluate expression " + res.toString(), e);
695                                    }
696                            }
697                            
698                            if (add && value != null && classGetter != null) {
699                                classGetter.initialize(null, null, value);
700                                
701                                int scope = 3;
702                                if (scopeType == ConversationScoped.class)
703                                    scope = 2;
704                                else if (scopeType == SessionScoped.class)
705                                    scope = 1;
706                                
707                                ContextUpdate cu = new ContextUpdate(res.getComponentName(), res.getExpression(), value, scope, Boolean.TRUE.equals(restrict));
708                                cu.setComponentClassName(res.getComponentClassName());
709                                resultsMap.add(cu);
710                                add = false;
711                            }
712                        }
713                        
714                        me.setValue(Boolean.FALSE);
715                    }
716                    
717                    return resultsMap;
718            }
719            finally {
720                    TideInvocation.get().unlock();
721            }
722        }
723    
724            @Inject
725            private CDIInitializer initializer;
726            
727            @Override
728            protected TidePersistenceManager getTidePersistenceManager(boolean create) {
729                    return initializer.getPersistenceManager();
730            }
731            
732            
733            @Override
734        protected boolean equals(Object obj1, Object obj2) {
735                    if (super.equals(obj1, obj2))
736                            return true;
737                    
738                    return (obj1 != null && obj2 != null 
739                                    && (obj1.getClass().isAnnotationPresent(TideBean.class) || obj2.getClass().isAnnotationPresent(TideBean.class)));
740        }
741            
742            
743            @SuppressWarnings("unchecked")
744            private static final Set<Class<?>> KNOWN_IMMUTABLES = new HashSet<Class<?>>(Arrays.asList(
745                            String.class, Byte.class, Short.class, Integer.class, Long.class,
746                    Float.class, Double.class, Boolean.class, BigInteger.class, BigDecimal.class
747            ));
748    
749            public static boolean isImmutable(Object o) {
750                return KNOWN_IMMUTABLES.contains(o.getClass()) || Enum.class.isInstance(o);
751            }
752        
753    //    /**
754    //     * Implementations of intercepted asynchronous calls
755    //     * Send asynchronous event to client
756    //     * @param asyncContext current context (session id)
757    //     * @param targetComponentName target component name
758    //     * @param methodName method name
759    //     * @param paramTypes method argument types
760    //     * @param params argument values
761    //     * @return result
762    //     */
763    //    public Object invokeAsynchronous(AsyncContext asyncContext, String targetComponentName, String methodName, Class<?>[] paramTypes, Object[] params) {
764    //        setAsyncContext(asyncContext);
765    //        
766    //        // Just another ugly hack: the Seam interceptor has set this variable and we don't want it
767    //        Contexts.getEventContext().remove("org.jboss.seam.async.AsynchronousIntercepter.REENTRANT");
768    //        
769    //        Component component = TideInit.lookupComponent(targetComponentName);
770    //        
771    //        // Forces evaluation of all results if they are related to the called component
772    //        for (Map.Entry<ContextResult, Boolean> me : getResultsEval(component.getScope()).entrySet()) {
773    //            if (me.getKey().getComponentName().equals(targetComponentName))
774    //                me.setValue(Boolean.TRUE);
775    //        }
776    //        
777    //        Object target = Component.getInstance(targetComponentName);
778    //        
779    //        Method method;
780    //        try {
781    //            method = target.getClass().getMethod(methodName, paramTypes);
782    //        }
783    //        catch (NoSuchMethodException nsme) {
784    //           throw new IllegalStateException(nsme);
785    //        }
786    //        
787    //        Object result = Reflections.invokeAndWrap(method, target, params);
788    //        
789    //        sendEvent(targetComponentName);
790    //        
791    //        return result;
792    //    }
793    //    
794    //
795    //    /**
796    //     * Search for a named attribute in all contexts, in the
797    //     * following order: method, event, page, conversation,
798    //     * session, business process, application.
799    //     * 
800    //     * @return the first component found, or null
801    //     */
802    //    public static Object[] lookupInStatefulContexts(String name, ScopeType scope) {
803    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.METHOD.equals(scope)) && Contexts.isMethodContextActive()) {
804    //            Object result = Contexts.getMethodContext().get(name);
805    //            if (result != null)
806    //                return new Object[] { result, Contexts.getMethodContext().getType() };
807    //        }
808    //        
809    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.EVENT.equals(scope)) && Contexts.isEventContextActive()) {
810    //            Object result = Contexts.getEventContext().get(name);
811    //            if (result != null)
812    //                return new Object[] { result, Contexts.getEventContext().getType() };
813    //        }
814    //        
815    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.PAGE.equals(scope)) && Contexts.isPageContextActive()) {
816    //            Object result = Contexts.getPageContext().get(name);
817    //            if (result != null)
818    //                return new Object[] { result, Contexts.getPageContext().getType() };
819    //        }
820    //        
821    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.CONVERSATION.equals(scope)) && Contexts.isConversationContextActive()) {
822    //            Object result = Contexts.getConversationContext().get(name);
823    //            if (result != null)
824    //                return new Object[] { result, Contexts.getConversationContext().getType() };
825    //        }
826    //        
827    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.SESSION.equals(scope)) && Contexts.isSessionContextActive()) {
828    //            Object result = Contexts.getSessionContext().get(name);
829    //            if (result != null)
830    //                return new Object[] { result, Contexts.getSessionContext().getType() };
831    //        }
832    //        
833    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.BUSINESS_PROCESS.equals(scope)) && Contexts.isBusinessProcessContextActive()) {
834    //            Object result = Contexts.getBusinessProcessContext().get(name);
835    //            if (result != null)
836    //                return new Object[] { result, Contexts.getBusinessProcessContext().getType() };
837    //        }
838    //        
839    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.APPLICATION.equals(scope)) && Contexts.isApplicationContextActive()) {
840    //            Object result = Contexts.getApplicationContext().get(name);
841    //            if (result != null)
842    //                return new Object[] { result, Contexts.getApplicationContext().getType() };
843    //        }
844    //        
845    //        return ScopeType.UNSPECIFIED.equals(scope) ? null : new Object[] { null, scope };
846    //    }
847    }