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 ires = new InvocationResult(result, results);
451            ires.setScope(scope);
452            ires.setRestrict(restrict);
453                    Set<Class<?>> componentClasses = findComponentClasses(componentName, componentClass);
454            if (isBeanAnnotationPresent(componentClasses, context.getMethod().getName(), context.getMethod().getParameterTypes(), BypassTideMerge.class))
455                            ires.setMerge(false);
456            
457            ires.setUpdates(updates);
458            
459            // Adds events in result object
460            ires.setEvents(tideInvocation.getEvents());
461            
462            try {
463                    // Save current set of entities loaded in a conversation scoped component to handle case of extended PM
464                    tideEntityInitializer.saveLoadedEntities();
465            }
466            catch (ContextNotActiveException e) {
467                    // Not in a conversation
468            }
469    
470            // Clean thread
471            TideInvocation.remove();
472            
473            return ires;
474        }
475    
476        /**
477         * Intercepts a fault on the invocation
478         * 
479         * @param context invocation context
480         * @param t exception thrown
481         * @param componentName name of the invoked component
482         */
483        @Override
484        public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
485            // Clean thread: very important to avoid phantom evaluations after exceptions
486            TideInvocation.remove();
487        }
488        
489        
490        public void addResultEval(ContextResult result) {
491            getResultsEval().put(result, Boolean.TRUE);
492        }
493        
494        
495        private static Object unproxy(Object obj) {
496            // TODO: Works only with Weld !!
497            if (obj instanceof TargetInstanceProxy<?>) {
498                    try {
499                            return ((TargetInstanceProxy<?>)obj).getTargetInstance();
500                    }
501                    catch (IllegalProductException e) {
502                            return null;
503                    }
504            }
505            return obj;
506        }
507        
508        /**
509         * Evaluate updates in current server context
510         * 
511         * @param updates list of updates
512         * @param target the target instance
513         */
514        public void restoreContext(List<ContextUpdate> updates, Object target) {
515            if (updates == null)
516                return;
517            
518            try {
519                    TideInvocation.get().lock();
520                    
521                    GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
522                    
523                    // Restore context
524                    for (ContextUpdate update : updates) {
525                        
526                        log.debug("Before invocation: evaluating expression #0(#1).#2", update.getComponentName(), update.getComponentClassName(), update.getExpression());
527                        
528                        Class<?> componentClass = update.getComponentClass();
529                        Bean<?> sourceBean = findBean(update.getComponentName(), componentClass);
530                        
531                        Object previous = null;
532                        if (update.getExpression() != null) {
533                            String[] path = update.getExpression().split("\\.");
534                            Object instance = manager.getReference(sourceBean, Object.class, manager.createCreationalContext(sourceBean));
535                            boolean disabled = instance != null 
536                                    && config.isComponentTideDisabled(sourceBean.getName(), beanClasses(sourceBean), instance);
537                            if (!disabled) {
538                                    instance = unproxy(instance);
539                                    
540                                Object bean = instance;
541                                Object value = instance;
542                                
543                                if (update.getValue() != null) {
544                                    boolean getPrevious = true;
545                                    if (update.getValue().getClass().isPrimitive() || isImmutable(update.getValue())) {
546                                            getPrevious = false;
547                                    }
548                                    else if (update.getValue().getClass().getAnnotation(Entity.class) != null) {
549                                        org.granite.util.Entity entity = new org.granite.util.Entity(update.getValue());
550                                        if (entity.getIdentifier() == null)
551                                            getPrevious = false;
552                                    }
553                                    if (getPrevious) {
554                                        try {
555                                            for (int i = 0; i < path.length; i++) {
556                                                if (value == null)
557                                                    break;
558                                                // Use modified Reflections for getter because of a bug in Seam 2.0.0
559                                                Method getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
560                                                value = Reflections.invoke(getter, value);
561                                                if (i < path.length-1)
562                                                    bean = value;
563                                            }
564                                        }
565                                        catch (IllegalArgumentException e) {
566                                            // No getter found to retrieve current value
567                                            log.warn(e, "Partial merge only");
568                                            value = null;
569                                        }
570                                        catch (Exception e) {
571                                            throw new ServiceException("Could not get property: " + update.toString(), e);
572                                        }
573                                        previous = value;
574                                    }
575                                }
576                                
577                                // Set new value
578                                try {
579                                    if (bean != null) {
580                                        Method setter = Reflections.getSetterMethod(bean.getClass(), path[path.length-1]);
581                                        Type type = setter.getParameterTypes()[0];
582                                        value = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters().convert(update.getValue(), type);
583                                        // Merge entities into current persistent context if needed
584                                        value = mergeExternal(value, previous);
585                                        Reflections.invoke(setter, bean, value);
586                                    }
587                                }
588                                catch (Exception e) {
589                                    throw new ServiceException("Could not restore property: " + update.toString(), e);
590                                }
591                            }
592                        }
593                        else {
594                            previous = manager.getReference(sourceBean, Object.class, manager.createCreationalContext(sourceBean));                 
595                            boolean disabled = previous != null 
596                                    && config.isComponentTideDisabled(sourceBean.getName(), beanClasses(sourceBean), previous);
597                            if (!disabled) {
598                                    previous = unproxy(previous);
599                                    
600                                    // Merge context variable
601                                    mergeExternal(update.getValue(), previous);
602                            }
603                        }
604                    }
605            }
606            finally {
607                    TideInvocation.get().unlock();
608            }
609        }
610        
611        
612        /**
613         * Evaluate results from context
614         * 
615         * @param target the target instance
616         * @param nothing used by initializer to avoid interactions with context sync
617         * 
618         * @return list of updates to send back to the client
619         */
620        public List<ContextUpdate> evaluateResults(Object target, boolean nothing) {
621            
622            List<ContextUpdate> resultsMap = new ArrayList<ContextUpdate>();
623            
624            if (nothing)
625                return resultsMap;
626            
627            try {
628                    TideInvocation.get().lock();
629                            
630                    GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
631                    ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
632                    
633                    for (Map.Entry<ContextResult, Boolean> me : getResultsEval().entrySet()) {
634                        if (!me.getValue())
635                            continue;
636                        
637                        ContextResult res = me.getKey();
638                        
639                        Class<?> componentClass = res.getComponentClass();
640                        Bean<?> targetBean = findBean(res.getComponentName(), componentClass);
641                        if (targetBean == null) {
642                            log.warn("Target bean " + res.getComponentName() + " of class " + componentClass + " not found");
643                            continue;
644                        }
645                        String targetComponentName = targetBean.getName();
646                        
647                        boolean add = true;
648                        Class<? extends Annotation> scopeType = targetBean.getScope();
649                        Boolean restrict = res.getRestrict();
650                        
651                        Object value = res instanceof ScopedContextResult 
652                            ? ((ScopedContextResult)res).getValue() 
653                            : manager.getReference(targetBean, Object.class, manager.createCreationalContext(targetBean));
654                        
655                        if (value != null && config.isComponentTideDisabled(targetComponentName, beanClasses(targetBean), value))
656                            add = false;
657                        
658                        if (add) {
659                            getResultsEval().put(res, false);
660                            
661                            String[] path = res.getExpression() != null ? res.getExpression().split("\\.") : new String[0];
662                            
663                            value = unproxy(value);
664                            
665                            if (value != null) {
666                                    try {
667                                        for (int i = 0; i < path.length; i++) {
668                                            if (value == null)
669                                                break;
670                                            try {
671                                                    Method getter = Reflections.getGetterMethod(value.getClass(), path[i]);
672                                                    value = Reflections.invoke(getter, value);
673                                            }
674                                            catch (IllegalArgumentException e) {
675                                                    // GDS-566
676                                                    add = false;
677                                            }                                   
678                                        }
679                                    }
680                                    catch (Exception e) {
681                                            throw new ServiceException("Could not evaluate expression " + res.toString(), e);
682                                    }
683                            }
684                            
685                            if (add && value != null && classGetter != null) {
686                                classGetter.initialize(null, null, value);
687                                
688                                int scope = 3;
689                                if (scopeType == ConversationScoped.class)
690                                    scope = 2;
691                                else if (scopeType == SessionScoped.class)
692                                    scope = 1;
693                                
694                                ContextUpdate cu = new ContextUpdate(res.getComponentName(), res.getExpression(), value, scope, Boolean.TRUE.equals(restrict));
695                                cu.setComponentClassName(res.getComponentClassName());
696                                resultsMap.add(cu);
697                                add = false;
698                            }
699                        }
700                        
701                        me.setValue(Boolean.FALSE);
702                    }
703                    
704                    return resultsMap;
705            }
706            finally {
707                    TideInvocation.get().unlock();
708            }
709        }
710    
711            @Inject
712            private CDIInitializer initializer;
713            
714            @Override
715            protected TidePersistenceManager getTidePersistenceManager(boolean create) {
716                    return initializer.getPersistenceManager();
717            }
718            
719            
720            @Override
721        protected boolean equals(Object obj1, Object obj2) {
722                    if (super.equals(obj1, obj2))
723                            return true;
724                    
725                    return (obj1 != null && obj2 != null 
726                                    && (obj1.getClass().isAnnotationPresent(TideBean.class) || obj2.getClass().isAnnotationPresent(TideBean.class)));
727        }
728            
729            
730            @SuppressWarnings("unchecked")
731            private static final Set<Class<?>> KNOWN_IMMUTABLES = new HashSet<Class<?>>(Arrays.asList(
732                            String.class, Byte.class, Short.class, Integer.class, Long.class,
733                    Float.class, Double.class, Boolean.class, BigInteger.class, BigDecimal.class
734            ));
735    
736            public static boolean isImmutable(Object o) {
737                return KNOWN_IMMUTABLES.contains(o.getClass()) || Enum.class.isInstance(o);
738            }
739        
740    //    /**
741    //     * Implementations of intercepted asynchronous calls
742    //     * Send asynchronous event to client
743    //     * @param asyncContext current context (session id)
744    //     * @param targetComponentName target component name
745    //     * @param methodName method name
746    //     * @param paramTypes method argument types
747    //     * @param params argument values
748    //     * @return result
749    //     */
750    //    public Object invokeAsynchronous(AsyncContext asyncContext, String targetComponentName, String methodName, Class<?>[] paramTypes, Object[] params) {
751    //        setAsyncContext(asyncContext);
752    //        
753    //        // Just another ugly hack: the Seam interceptor has set this variable and we don't want it
754    //        Contexts.getEventContext().remove("org.jboss.seam.async.AsynchronousIntercepter.REENTRANT");
755    //        
756    //        Component component = TideInit.lookupComponent(targetComponentName);
757    //        
758    //        // Forces evaluation of all results if they are related to the called component
759    //        for (Map.Entry<ContextResult, Boolean> me : getResultsEval(component.getScope()).entrySet()) {
760    //            if (me.getKey().getComponentName().equals(targetComponentName))
761    //                me.setValue(Boolean.TRUE);
762    //        }
763    //        
764    //        Object target = Component.getInstance(targetComponentName);
765    //        
766    //        Method method;
767    //        try {
768    //            method = target.getClass().getMethod(methodName, paramTypes);
769    //        }
770    //        catch (NoSuchMethodException nsme) {
771    //           throw new IllegalStateException(nsme);
772    //        }
773    //        
774    //        Object result = Reflections.invokeAndWrap(method, target, params);
775    //        
776    //        sendEvent(targetComponentName);
777    //        
778    //        return result;
779    //    }
780    //    
781    //
782    //    /**
783    //     * Search for a named attribute in all contexts, in the
784    //     * following order: method, event, page, conversation,
785    //     * session, business process, application.
786    //     * 
787    //     * @return the first component found, or null
788    //     */
789    //    public static Object[] lookupInStatefulContexts(String name, ScopeType scope) {
790    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.METHOD.equals(scope)) && Contexts.isMethodContextActive()) {
791    //            Object result = Contexts.getMethodContext().get(name);
792    //            if (result != null)
793    //                return new Object[] { result, Contexts.getMethodContext().getType() };
794    //        }
795    //        
796    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.EVENT.equals(scope)) && Contexts.isEventContextActive()) {
797    //            Object result = Contexts.getEventContext().get(name);
798    //            if (result != null)
799    //                return new Object[] { result, Contexts.getEventContext().getType() };
800    //        }
801    //        
802    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.PAGE.equals(scope)) && Contexts.isPageContextActive()) {
803    //            Object result = Contexts.getPageContext().get(name);
804    //            if (result != null)
805    //                return new Object[] { result, Contexts.getPageContext().getType() };
806    //        }
807    //        
808    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.CONVERSATION.equals(scope)) && Contexts.isConversationContextActive()) {
809    //            Object result = Contexts.getConversationContext().get(name);
810    //            if (result != null)
811    //                return new Object[] { result, Contexts.getConversationContext().getType() };
812    //        }
813    //        
814    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.SESSION.equals(scope)) && Contexts.isSessionContextActive()) {
815    //            Object result = Contexts.getSessionContext().get(name);
816    //            if (result != null)
817    //                return new Object[] { result, Contexts.getSessionContext().getType() };
818    //        }
819    //        
820    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.BUSINESS_PROCESS.equals(scope)) && Contexts.isBusinessProcessContextActive()) {
821    //            Object result = Contexts.getBusinessProcessContext().get(name);
822    //            if (result != null)
823    //                return new Object[] { result, Contexts.getBusinessProcessContext().getType() };
824    //        }
825    //        
826    //        if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.APPLICATION.equals(scope)) && Contexts.isApplicationContextActive()) {
827    //            Object result = Contexts.getApplicationContext().get(name);
828    //            if (result != null)
829    //                return new Object[] { result, Contexts.getApplicationContext().getType() };
830    //        }
831    //        
832    //        return ScopeType.UNSPECIFIED.equals(scope) ? null : new Object[] { null, scope };
833    //    }
834    }