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