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     */
022    package org.granite.tide.cdi;
023    
024    import java.lang.annotation.Annotation;
025    import java.lang.reflect.Method;
026    import java.lang.reflect.Type;
027    import java.math.BigDecimal;
028    import java.math.BigInteger;
029    import java.util.ArrayList;
030    import java.util.Arrays;
031    import java.util.HashSet;
032    import java.util.List;
033    import java.util.Map;
034    import java.util.Set;
035    
036    import javax.annotation.PreDestroy;
037    import javax.enterprise.context.ContextNotActiveException;
038    import javax.enterprise.context.ConversationScoped;
039    import javax.enterprise.context.RequestScoped;
040    import javax.enterprise.context.SessionScoped;
041    import javax.enterprise.context.spi.CreationalContext;
042    import javax.enterprise.inject.Any;
043    import javax.enterprise.inject.Default;
044    import javax.enterprise.inject.IllegalProductException;
045    import javax.enterprise.inject.spi.Bean;
046    import javax.enterprise.inject.spi.BeanManager;
047    import javax.enterprise.util.AnnotationLiteral;
048    import javax.inject.Inject;
049    import javax.persistence.Entity;
050    import javax.servlet.http.HttpSession;
051    
052    import org.granite.config.GraniteConfig;
053    import org.granite.context.GraniteContext;
054    import org.granite.logging.Logger;
055    import org.granite.messaging.amf.io.util.ClassGetter;
056    import org.granite.messaging.service.ServiceException;
057    import org.granite.messaging.service.ServiceInvocationContext;
058    import org.granite.messaging.webapp.HttpGraniteContext;
059    import org.granite.tide.IInvocationCall;
060    import org.granite.tide.IInvocationResult;
061    import org.granite.tide.TidePersistenceManager;
062    import org.granite.tide.TideServiceContext;
063    import org.granite.tide.annotations.BypassTideMerge;
064    import org.granite.tide.async.AsyncPublisher;
065    import org.granite.tide.cdi.lazy.CDIInitializer;
066    import org.granite.tide.data.DataContext;
067    import org.granite.tide.data.DataUpdatePostprocessor;
068    import org.granite.tide.invocation.ContextEvent;
069    import org.granite.tide.invocation.ContextResult;
070    import org.granite.tide.invocation.ContextUpdate;
071    import org.granite.tide.invocation.InvocationCall;
072    import org.granite.tide.invocation.InvocationResult;
073    import org.granite.util.TypeUtil;
074    import org.granite.util.Reflections;
075    import org.jboss.interceptor.util.proxy.TargetInstanceProxy;
076    
077    
078    /**
079     * @author William DRAI
080     */
081    @SessionScoped
082    public 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    }