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