001 /*
002 GRANITE DATA SERVICES
003 Copyright (C) 2007-2010 ADEQUATE SYSTEMS SARL
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.seam;
022
023 import java.lang.reflect.Field;
024 import java.lang.reflect.Method;
025 import java.lang.reflect.Type;
026 import java.util.ArrayList;
027 import java.util.HashMap;
028 import java.util.HashSet;
029 import java.util.List;
030 import java.util.Map;
031 import java.util.Set;
032
033 import javax.persistence.Entity;
034 import javax.servlet.http.HttpSession;
035
036 import org.granite.config.GraniteConfig;
037 import org.granite.context.GraniteContext;
038 import org.granite.messaging.amf.io.util.ClassGetter;
039 import org.granite.messaging.service.ServiceException;
040 import org.granite.messaging.service.ServiceInvocationContext;
041 import org.granite.messaging.webapp.HttpGraniteContext;
042 import org.granite.tide.IInvocationCall;
043 import org.granite.tide.IInvocationResult;
044 import org.granite.tide.TidePersistenceManager;
045 import org.granite.tide.TideServiceContext;
046 import org.granite.tide.TideStatusMessages;
047 import org.granite.tide.annotations.BypassTideMerge;
048 import org.granite.tide.async.AsyncPublisher;
049 import org.granite.tide.data.DataContext;
050 import org.granite.tide.data.DataMergeContext;
051 import org.granite.tide.invocation.ContextEvent;
052 import org.granite.tide.invocation.ContextResult;
053 import org.granite.tide.invocation.ContextUpdate;
054 import org.granite.tide.invocation.InvocationCall;
055 import org.granite.tide.invocation.InvocationResult;
056 import org.granite.tide.seam.async.AsyncContext;
057 import org.granite.tide.seam.lazy.SeamInitializer;
058 import org.jboss.seam.Component;
059 import org.jboss.seam.ScopeType;
060 import org.jboss.seam.annotations.Destroy;
061 import org.jboss.seam.annotations.Factory;
062 import org.jboss.seam.annotations.Logger;
063 import org.jboss.seam.annotations.Observer;
064 import org.jboss.seam.annotations.datamodel.DataModelSelection;
065 import org.jboss.seam.annotations.security.Restrict;
066 import org.jboss.seam.contexts.Context;
067 import org.jboss.seam.contexts.Contexts;
068 import org.jboss.seam.core.Conversation;
069 import org.jboss.seam.core.Init.FactoryExpression;
070 import org.jboss.seam.core.Init.FactoryMethod;
071 import org.jboss.seam.framework.Home;
072 import org.jboss.seam.log.Log;
073 import org.jboss.seam.security.Identity;
074 import org.jboss.seam.util.Reflections;
075
076
077 /**
078 * @author William DRAI
079 */
080 public abstract class AbstractSeamServiceContext extends TideServiceContext {
081
082 private static final long serialVersionUID = 1L;
083
084 public static final String COMPONENT_NAME = "org.granite.tide.seam.serviceContext";
085
086 protected @Logger Log log;
087
088 private UserEvents userEvents;
089 private boolean isAsynchronousContext = true;
090
091
092 private static final String RESULTS_EVAL_ATTRIBUTE = AbstractSeamServiceContext.class.getName() + "_resultsEval";
093 private static final String RESULTS_EVAL_UNSPECIFIED_ATTRIBUTE = AbstractSeamServiceContext.class.getName() + "_resultsEval_unspecified";
094
095
096 public AbstractSeamServiceContext() throws ServiceException {
097 super();
098 }
099
100 /**
101 * Determines the current sessionId for web invocations
102 */
103 @Override
104 public void initCall() {
105 super.initCall();
106
107 if (userEvents != null)
108 return;
109
110 if (getSessionId() != null)
111 userEvents = TideUserEvents.instance().getUserEvents(getSessionId());
112 else {
113 GraniteContext graniteContext = GraniteContext.getCurrentInstance();
114 if (graniteContext instanceof HttpGraniteContext) {
115 HttpSession session = ((HttpGraniteContext)graniteContext).getSession(false);
116 if (session != null)
117 setSessionId(session.getId());
118 isAsynchronousContext = false;
119 }
120 }
121 }
122
123 /**
124 * Initialize current sessionId and event listeners for this context
125 *
126 * @param sessionId current sessionId
127 */
128 @Override
129 public void setSessionId(String sessionId) {
130 super.setSessionId(sessionId);
131 userEvents = TideUserEvents.instance().getUserEvents(sessionId);
132 }
133
134 /**
135 * Clear current session from user events registry
136 */
137 @Destroy
138 public void endSession() {
139 if (!isAsynchronousContext && getSessionId() != null)
140 TideUserEvents.instance().unregisterSession(getSessionId());
141 }
142
143
144 private Map<ContextResult, Boolean> getResultsEval(ScopeType scopeType) {
145 Context context = Contexts.getEventContext();
146 String att = RESULTS_EVAL_UNSPECIFIED_ATTRIBUTE;
147 if (scopeType == ScopeType.STATELESS)
148 att = RESULTS_EVAL_ATTRIBUTE;
149 else if (scopeType != ScopeType.UNSPECIFIED) {
150 context = scopeType.getContext();
151 att = RESULTS_EVAL_ATTRIBUTE;
152 }
153
154 @SuppressWarnings("unchecked")
155 Map<ContextResult, Boolean> resultsEval = (Map<ContextResult, Boolean>)context.get(att);
156 if (resultsEval == null) {
157 resultsEval = new HashMap<ContextResult, Boolean>();
158 context.set(att, resultsEval);
159 }
160 return resultsEval;
161 }
162
163
164 /**
165 * Constructs an asynchronous context object
166 * @return current context
167 */
168 public AsyncContext getAsyncContext() {
169 List<ContextResult> resultsEval = new ArrayList<ContextResult>();
170 for (ScopeType evalScopeType : EVAL_SCOPE_TYPES)
171 resultsEval.addAll(getResultsEval(evalScopeType).keySet());
172
173 return new AsyncContext(getSessionId(), resultsEval);
174 }
175
176 /**
177 * Restores an asynchronous context
178 * @param asyncContext saved context
179 */
180 public void setAsyncContext(AsyncContext asyncContext) {
181 AsyncPublisher asyncPublisher = getAsyncPublisher();
182 if (asyncPublisher != null)
183 asyncPublisher.initThread();
184
185 Contexts.getSessionContext().set("org.jboss.seam.security.identity", asyncContext.getIdentity());
186 setSessionId(asyncContext.getSessionId());
187 for (ContextResult resultEval : asyncContext.getResults()) {
188 if (resultEval instanceof ScopedContextResult)
189 getResultsEval(((ScopedContextResult)resultEval).getScope()).put(resultEval, Boolean.FALSE);
190 else
191 getResultsEval(ScopeType.UNSPECIFIED).put(resultEval, Boolean.FALSE);
192 }
193 }
194
195
196 /**
197 * Retrieve current messages
198 *
199 * @return list of messages
200 */
201 protected abstract TideStatusMessages getTideMessages();
202
203 protected abstract void initTideMessages();
204
205 protected abstract void clearTideMessages();
206
207
208 /**
209 * Implementation of component lookup for Seam service
210 *
211 * @param componentName component name
212 */
213 @Override
214 public Object findComponent(String componentName, Class<?> componentClass) {
215 Component component = TideInit.lookupComponent(componentName);
216 if (component == null)
217 return null;
218 return Component.getInstance(component.getName());
219 }
220
221 /**
222 * Implementation of component lookup for Seam service
223 *
224 * @param componentName component name
225 */
226 @Override
227 public Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass) {
228 Component component = TideInit.lookupComponent(componentName);
229 if (component == null)
230 return null;
231
232 return componentClasses(component);
233 }
234
235 private Set<Class<?>> componentClasses(Object component) {
236 if (component instanceof Component) {
237 Set<Class<?>> classes = new HashSet<Class<?>>();
238 for (Class<?> i : ((Component)component).getBusinessInterfaces())
239 classes.add(i);
240 classes.add(((Component)component).getBeanClass());
241 return classes;
242 }
243
244 Set<Class<?>> classes = new HashSet<Class<?>>(1);
245 classes.add(component.getClass());
246 return classes;
247 }
248
249
250 @Observer("org.jboss.seam.beginConversation")
251 public void observeBeginConversation() {
252 Contexts.getEventContext().set("org.granite.tide.conversation.wasCreated", true);
253 }
254
255
256 /**
257 * Add an event in the current context
258 *
259 * @param type event type
260 * @param params event parameters
261 */
262 public void raiseEvent(String type, Object... params) {
263 // Add the event to the current invocation
264 TideInvocation tideInvocation = TideInvocation.get();
265 if (tideInvocation == null)
266 return;
267
268 if (userEvents != null) {
269 // Avoid stupid infinite loop when creating locale selector as the log needs the current locale...
270 if (!type.endsWith("org.jboss.seam.international.localeSelector"))
271 log.debug("Intercept event %s", type);
272 String sessionId = getSessionId();
273 if (sessionId != null && userEvents.hasEventType(type))
274 tideInvocation.addEvent(new ContextEvent(type, params));
275 }
276 else if (Contexts.getSessionContext().isSet("org.granite.seam.login")) {
277 // Force send of all events raised during login
278 tideInvocation.addEvent(new ContextEvent(type, params));
279 }
280 }
281
282
283 /**
284 * Factory for Seam async publisher
285 *
286 * @return servlet context of the current application
287 */
288 @Override
289 protected AsyncPublisher getAsyncPublisher() {
290 return (AsyncPublisher)Component.getInstance("org.granite.tide.seam.async.publisher");
291 }
292
293
294 /**
295 * Synchronizes server context with data provided by the client
296 *
297 * @param context invocation context
298 * @param c client call
299 * @param componentName name of the component which will be invoked
300 */
301 @Override
302 public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) {
303 InvocationCall call = (InvocationCall)c;
304 List<String> listeners = call.getListeners();
305 List<ContextUpdate> updates = call.getUpdates();
306 Object[] results = call.getResults();
307
308 if (Contexts.isEventContextActive() && Contexts.isSessionContextActive() &&
309 (Contexts.getSessionContext().isSet("org.granite.tide.isFirstCall") || Contexts.getSessionContext().isSet("org.granite.seam.login"))) {
310 // Login tried : force evaluation of existing session context
311 for (Map.Entry<ContextResult, Boolean> me : getResultsEval(ScopeType.SESSION).entrySet()) {
312 if (me.getKey().getExpression() == null && Contexts.getSessionContext().isSet(me.getKey().getComponentName()))
313 me.setValue(Boolean.TRUE);
314 }
315 Contexts.getSessionContext().remove("org.granite.seam.login");
316 Contexts.getSessionContext().remove("org.granite.tide.isFirstCall");
317
318 // Force quiet login
319 if (Identity.instance().isLoggedIn() && Contexts.isEventContextActive())
320 Contexts.getEventContext().set("org.jboss.seam.security.silentLogin", true);
321 }
322 if (Contexts.isEventContextActive() && Contexts.isConversationContextActive() &&
323 Contexts.getConversationContext().isSet("org.granite.tide.isFirstConversationCall")) {
324 // Join conversation : force evaluation of existing conversation context
325 for (Map.Entry<ContextResult, Boolean> me : getResultsEval(ScopeType.CONVERSATION).entrySet()) {
326 if (me.getKey().getExpression() == null && Contexts.getConversationContext().isSet(me.getKey().getComponentName()))
327 me.setValue(Boolean.TRUE);
328 }
329 Contexts.getConversationContext().remove("org.granite.tide.isFirstConversationCall");
330 }
331
332 String sessionId = getSessionId();
333 if (sessionId != null && listeners != null) {
334 // Registers new event listeners
335 for (String listener : listeners)
336 TideUserEvents.instance().registerEventType(sessionId, listener);
337
338 if (userEvents == null)
339 userEvents = TideUserEvents.instance().getUserEvents(getSessionId());
340 }
341
342 if (results != null) {
343 Map<ContextResult, Boolean> resultsEval = getResultsEval(ScopeType.UNSPECIFIED);
344 for (Object result : results) {
345 ContextResult cr = (ContextResult)result;
346 resultsEval.put(new ScopedContextResult(cr.getComponentName(), cr.getExpression(), ScopeType.UNSPECIFIED, null), Boolean.TRUE);
347
348 // if (!factories.containsKey(cr.getComponentName())) {
349 // FactoryExpression expr = Init.instance().getFactoryValueExpression(cr.getComponentName());
350 // if (expr != null) {
351 // String vexpr = expr.getValueBinding().getExpressionString();
352 // vexpr = vexpr.substring(2, vexpr.indexOf('.', 2));
353 // factories.put(cr.getComponentName(), vexpr);
354 //
355 // resultsEval.put(new ContextResult(cr.getComponentName(), null), Boolean.TRUE);
356 // }
357 // }
358 }
359 }
360
361 boolean instrumented = false;
362 Component component = null;
363 if (componentName != null) {
364 component = TideInit.lookupComponent(componentName);
365 if (component.isInterceptionEnabled())
366 instrumented = true;
367
368 // Forces evaluation of all results if they are related to the called component
369 for (Map.Entry<ContextResult, Boolean> me : getResultsEval(component.getScope()).entrySet()) {
370 if (me.getKey().getComponentName().equals(componentName))
371 // || componentName.equals(factories.get(me.getKey().getComponentName())))
372 me.setValue(Boolean.TRUE);
373 }
374 }
375
376 initTideMessages();
377
378 SeamInitializer.instance().restoreLoadedEntities();
379
380 if (Conversation.instance().isLongRunning()) {
381 // Merge call arguments with current conversation context
382 Object[] args = context.getParameters();
383 if (args != null) {
384 for (int i = 0; i < args.length; i++) {
385 Object value = mergeExternal(args[i], null);
386 args[i] = value;
387 }
388 }
389 }
390
391 TideInvocation tideInvocation = TideInvocation.init();
392 tideInvocation.update(updates);
393
394 if (!instrumented) {
395 // If no interception enabled, force the update of the context for the current component
396 // In other cases it will be done by the interceptor
397 restoreContext(updates, component, null);
398 tideInvocation.updated();
399 }
400 }
401
402 /**
403 * Builds the result object for the invocation
404 *
405 * @param context invocation context
406 * @param result result of the method invocation
407 * @param componentName name of the invoked component
408 * @return result object
409 */
410 @Override
411 public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) {
412 TideInvocation tideInvocation = TideInvocation.get();
413 List<ContextUpdate> results = null;
414 int scope = 3;
415 boolean restrict = false;
416
417 Component component = null;
418 if (componentName != null) {
419 // Determines scope of component
420 component = TideInit.lookupComponent(componentName);
421 if (Contexts.isMethodContextActive() && Contexts.getMethodContext().isSet(component.getName()))
422 scope = 3;
423 else if (Contexts.isEventContextActive() && Contexts.getEventContext().isSet(component.getName()))
424 scope = 3;
425 else if (Contexts.isPageContextActive() && Contexts.getPageContext().isSet(component.getName()))
426 scope = 3;
427 else if (Contexts.isConversationContextActive() && Contexts.getConversationContext().isSet(component.getName()))
428 scope = 2;
429
430 restrict = component.beanClassHasAnnotation(Restrict.class);
431 }
432
433 if (!tideInvocation.isEvaluated()) {
434 // Do evaluation now if the interceptor has not been called
435 results = evaluateResults(null, null, componentName == null
436 && !(context != null && context.getMethod() != null && "resyncContext".equals(context.getMethod().getName())));
437 }
438 else
439 results = tideInvocation.getResults();
440
441 DataContext dataContext = DataContext.get();
442 Set<Object[]> dataUpdates = dataContext != null ? dataContext.getDataUpdates() : null;
443 Object[][] updates = null;
444 if (dataUpdates != null && !dataUpdates.isEmpty())
445 updates = dataUpdates.toArray(new Object[dataUpdates.size()][]);
446
447 InvocationResult res = new InvocationResult(result, results);
448 res.setScope(scope);
449 res.setRestrict(restrict);
450 if (component != null) {
451 if (component.beanClassHasAnnotation(BypassTideMerge.class))
452 res.setMerge(false);
453 else {
454 try {
455 if (context != null) {
456 Method m = component.getBeanClass().getMethod(context.getMethod().getName(), context.getMethod().getParameterTypes());
457 if (m.isAnnotationPresent(BypassTideMerge.class))
458 res.setMerge(false);
459 }
460 }
461 catch (Exception e) {
462 log.warn("Could not find bean method", e);
463 }
464 }
465 }
466
467 if (Conversation.instance().isLongRunning()) {
468 // Put results in merge context to keep data in extended persistence context between remote calls
469 DataMergeContext.addResultEntity(result);
470 for (ContextUpdate cu : results)
471 DataMergeContext.addResultEntity(cu.getValue());
472 }
473
474 res.setUpdates(updates);
475
476 // Adds events in result object
477 res.setEvents(tideInvocation.getEvents());
478
479 // Save current set of entities loaded in a conversation scoped component to handle case of extended PM
480 SeamInitializer.instance().saveLoadedEntities();
481
482 // Adds context messages in result object
483 TideStatusMessages statusMessages = getTideMessages();
484 res.setMessages(statusMessages.getMessages());
485 res.setKeyedMessages(statusMessages.getKeyedMessages());
486
487 clearTideMessages();
488
489 // Clean thread
490 TideInvocation.remove();
491
492 return res;
493 }
494
495 /**
496 * Intercepts a fault on the invocation
497 *
498 * @param context invocation context
499 * @param t exception thrown
500 * @param componentName name of the invoked component
501 */
502 @Override
503 public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
504 clearTideMessages();
505
506 // Clean thread: very important to avoid phantom evaluations after exceptions
507 TideInvocation.remove();
508 }
509
510
511 public void addResultEval(ScopedContextResult result) {
512 getResultsEval(result.getScope()).put(result, Boolean.TRUE);
513 }
514
515
516 /**
517 * Evaluate updates in current server context
518 *
519 * @param updates list of updates
520 * @param component the target component
521 * @param target the target instance
522 */
523 public void restoreContext(List<ContextUpdate> updates, Component component, Object target) {
524 if (updates == null)
525 return;
526
527 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
528
529 // Restore context
530 for (ContextUpdate update : updates) {
531
532 log.debug("Before invocation: evaluating expression #0.#1", update.getComponentName(), update.getExpression());
533
534 Component sourceComponent = TideInit.lookupComponent(update.getComponentName());
535 String sourceComponentName = sourceComponent != null ? sourceComponent.getName() : update.getComponentName();
536
537 Object previous = null;
538 if (update.getExpression() != null) {
539 // Set component property
540 // Ignore expression on external components if target component is not interception enabled
541 // to avoid issues with bijection, persistence contexts and transactions
542 if (target != null || (component != null && component.getName().equals(sourceComponentName))) {
543 // Get current values in Seam context
544 String[] path = update.getExpression().split("\\.");
545 Object instance = component != null && component.getName().equals(sourceComponentName) && target != null
546 ? target
547 : Component.getInstance(sourceComponentName);
548 boolean disabled = instance != null && sourceComponent != null &&
549 config.isComponentTideDisabled(sourceComponentName, componentClasses(sourceComponent), instance);
550 if (!disabled) {
551 Object bean = instance;
552 Object value = instance;
553
554 List<Field> dmsFields = instance != null && path.length == 1
555 ? org.granite.util.Reflections.getFields(instance.getClass(), DataModelSelection.class) : null;
556 List<String> dmsFieldNames = null;
557 if (dmsFields != null && !dmsFields.isEmpty()) {
558 dmsFieldNames = new ArrayList<String>(dmsFields.size());
559 for (Field f : dmsFields)
560 dmsFieldNames.add(f.getName());
561 }
562
563 if (update.getValue() != null) {
564 boolean getPrevious = true;
565 if (update.getValue().getClass().getAnnotation(Entity.class) != null) {
566 org.granite.util.Entity entity = new org.granite.util.Entity(update.getValue());
567 if (entity.getIdentifier() == null)
568 getPrevious = false;
569 }
570 if (getPrevious) {
571 try {
572 for (int i = 0; i < path.length; i++) {
573 if (value == null)
574 break;
575 if (i == 0 && dmsFieldNames != null && dmsFieldNames.contains(path[0])) {
576 Field field = org.granite.util.Reflections.getField(value.getClass(), path[i]);
577 field.setAccessible(true);
578 value = Reflections.get(field, value);
579 if (i < path.length-1)
580 bean = value;
581 }
582 else {
583 // Use modified Reflections for getter because of a bug in Seam 2.0.0
584 Method getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
585 value = Reflections.invoke(getter, value);
586 if (i < path.length-1)
587 bean = value;
588 }
589 }
590 }
591 catch (IllegalArgumentException e) {
592 // No getter found to retrieve current value
593 log.warn("Partial merge only: " + e.getMessage());
594 value = null;
595 }
596 catch (Exception e) {
597 throw new RuntimeException("Could not get property: " + update.toString(), e);
598 }
599 previous = value;
600 }
601 }
602
603 // Set new value
604 try {
605 if (bean != null && path.length == 1 && dmsFieldNames != null && dmsFieldNames.contains(path[0])) {
606 Field field = org.granite.util.Reflections.getField(bean.getClass(), path[0]);
607 field.setAccessible(true);
608 value = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters().convert(update.getValue(), field.getType());
609 // Merge entities into current persistent context if needed
610 value = mergeExternal(value, previous);
611 Reflections.set(field, bean, value);
612 }
613 else if (bean != null) {
614 Method setter = Reflections.getSetterMethod(bean.getClass(), path[path.length-1]);
615 Type type = setter.getParameterTypes()[0];
616 if (bean instanceof Home<?, ?> && "id".equals(path[path.length-1])) {
617 // Special (ugly ?) handling for Home object to try to guess id type (setId is of type Object)
618 try {
619 Class<?> entityClass = ((Home<?, ?>)bean).getEntityClass();
620 org.granite.util.Entity entity = new org.granite.util.Entity(entityClass);
621 type = entity.getIdentifierType();
622 }
623 catch (Exception e) {
624 // Ignore
625 }
626 }
627 value = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters().convert(update.getValue(), type);
628 // Merge entities into current persistent context if needed
629 value = mergeExternal(value, previous);
630 Reflections.invoke(setter, bean, value);
631 }
632 }
633 catch (Exception e) {
634 throw new RuntimeException("Could not restore property: " + update.toString(), e);
635 }
636 }
637 }
638 }
639 else {
640 // Set context variable
641 if (sourceComponent != null) {
642 ScopeType scope = sourceComponent.getScope();
643 if (!((update.getScope() == 2 && (scope == ScopeType.EVENT
644 || scope == ScopeType.STATELESS
645 || scope == ScopeType.CONVERSATION
646 || scope == ScopeType.BUSINESS_PROCESS
647 || scope == ScopeType.METHOD
648 || scope == ScopeType.PAGE))
649 || (update.getScope() == 1 && (scope == ScopeType.EVENT
650 || scope == ScopeType.STATELESS
651 || scope == ScopeType.SESSION
652 || scope == ScopeType.METHOD
653 || scope == ScopeType.PAGE))
654 || (update.getScope() == 3 && (scope == ScopeType.EVENT
655 || scope == ScopeType.STATELESS
656 || scope == ScopeType.METHOD
657 || scope == ScopeType.PAGE)))) {
658 scope = ScopeType.EVENT;
659 }
660
661 previous = scope.getContext().get(sourceComponentName);
662
663 boolean disabled = previous != null && config.isComponentTideDisabled(sourceComponentName, componentClasses(sourceComponent), previous);
664 if (!disabled) {
665 Object value = mergeExternal(update.getValue(), previous);
666
667 scope.getContext().set(sourceComponentName, value);
668 }
669 }
670 else {
671 Object[] prev = lookupInStatefulContexts(sourceComponentName, ScopeType.UNSPECIFIED);
672 ScopeType scope = ScopeType.UNSPECIFIED;
673 if (prev != null) {
674 previous = prev[0];
675 scope = (ScopeType)prev[1];
676 }
677
678 boolean disabled = previous != null && config.isComponentTideDisabled(sourceComponentName,
679 componentClasses(previous), previous);
680 if (!disabled) {
681 if (scope == ScopeType.UNSPECIFIED) {
682 scope = ScopeType.EVENT;
683 scope.getContext().set(sourceComponentName + "_tide_unspecified_", true);
684 }
685
686 Object value = mergeExternal(update.getValue(), previous);
687
688 scope.getContext().set(sourceComponentName, value);
689 }
690 }
691 }
692 }
693 }
694
695
696 private static final ScopeType[] EVAL_SCOPE_TYPES = {
697 ScopeType.UNSPECIFIED,
698 ScopeType.EVENT,
699 ScopeType.CONVERSATION,
700 ScopeType.SESSION,
701 ScopeType.BUSINESS_PROCESS,
702 ScopeType.APPLICATION
703 };
704
705 /**
706 * Evaluate results from context
707 *
708 * @param component the target component
709 * @param target the target instance
710 * @param nothing used by initializer to avoid interactions with context sync
711 *
712 * @return list of updates to send back to the client
713 */
714 public List<ContextUpdate> evaluateResults(Component component, Object target, boolean nothing) {
715
716 List<ContextUpdate> resultsMap = new ArrayList<ContextUpdate>();
717
718 if (nothing)
719 return resultsMap;
720
721 List<String> exprs = new ArrayList<String>();
722 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
723 ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
724
725 SeamInitializer.instance();
726
727 for (ScopeType evalScopeType : EVAL_SCOPE_TYPES) {
728 for (Map.Entry<ContextResult, Boolean> me : getResultsEval(evalScopeType).entrySet()) {
729 if (!me.getValue())
730 continue;
731
732 ContextResult res = me.getKey();
733 Component targetComponent = TideInit.lookupComponent(res.getComponentName());
734
735 String targetComponentName = targetComponent != null ? targetComponent.getName() : res.getComponentName();
736
737 if (res.getExpression() != null && component != null && targetComponent != null && !(component.getName().equals(targetComponentName))) {
738 // Ignore results concerning other components for this time
739 // In this case is has to be a non interception enabled component
740 continue;
741 }
742
743 int idx = 0;
744 boolean add = true;
745
746 // Force evaluation of consecutive properties
747 while (idx >= 0) {
748 idx = res.getExpression() != null ? res.getExpression().indexOf(".", idx+1) : -1;
749
750 String expr = (idx > 0 ? res.getExpression().substring(0, idx) : res.getExpression());
751 String ex = expr != null ? res.getComponentName() + "." + expr : res.getComponentName();
752
753 if (!exprs.contains(ex)) {
754 log.debug("After invocation: evaluating expression #0", ex);
755
756 String[] path = expr != null ? expr.split("\\.") : new String[0];
757
758 try {
759 Object value = null;
760 ScopeType scopeType = res instanceof ScopedContextResult ? ((ScopedContextResult)res).getScope() : ScopeType.UNSPECIFIED;
761 Boolean restrict = res.getRestrict();
762
763 FactoryMethod factoryMethod = null;
764 FactoryExpression factoryExpression = null;
765 if (targetComponent == null) {
766 factoryExpression = TideInit.lookupFactoryExpression(res.getComponentName());
767 if (factoryExpression == null)
768 factoryMethod = TideInit.lookupFactory(res.getComponentName());
769 }
770
771 if (targetComponent != null) {
772 if (component != null && component.getName().equals(targetComponent.getName())) {
773 value = target;
774 scopeType = targetComponent.getScope();
775 }
776 else if (ScopeType.UNSPECIFIED.equals(scopeType)) {
777 value = Component.getInstance(targetComponent.getName());
778 scopeType = targetComponent.getScope();
779 if (ScopeType.STATELESS.equals(scopeType))
780 scopeType = ScopeType.EVENT;
781
782 if (value != null && config.isComponentTideDisabled(targetComponentName,
783 componentClasses(targetComponent), value))
784 add = false;
785 }
786 else {
787 value = Component.getInstance(targetComponent.getName(), scopeType);
788
789 if (value != null && config.isComponentTideDisabled(targetComponentName,
790 componentClasses(value), value))
791 add = false;
792 }
793
794 restrict = targetComponent.beanClassHasAnnotation(Restrict.class);
795 }
796 else if (factoryExpression != null) {
797 String expressionString = factoryExpression.getMethodBinding() != null
798 ? factoryExpression.getMethodBinding().getExpressionString()
799 : factoryExpression.getValueBinding().getExpressionString();
800 int iedx = expressionString.indexOf(".");
801 String expressionBaseName = expressionString.substring(2, iedx);
802
803 if (ScopeType.UNSPECIFIED.equals(scopeType)) {
804 value = Component.getInstance(res.getComponentName());
805 scopeType = factoryExpression.getScope();
806 if (ScopeType.STATELESS.equals(scopeType))
807 scopeType = ScopeType.EVENT;
808
809 if (value != null && config.isComponentTideDisabled(expressionBaseName, componentClasses(value), value))
810 add = false;
811 }
812 else {
813 value = Component.getInstance(res.getComponentName(), scopeType);
814
815 if (value != null && config.isComponentTideDisabled(expressionBaseName, componentClasses(value), value))
816 add = false;
817 }
818
819 Component factoryComponent = TideInit.lookupComponent(expressionBaseName);
820 restrict = factoryComponent != null ? factoryComponent.beanClassHasAnnotation(Restrict.class) : false;
821 }
822 else if (factoryMethod != null) {
823 if (ScopeType.UNSPECIFIED.equals(scopeType)) {
824 value = Component.getInstance(factoryMethod.getMethod().getAnnotation(Factory.class).value());
825 scopeType = factoryMethod.getScope();
826 if (ScopeType.STATELESS.equals(scopeType))
827 scopeType = ScopeType.EVENT;
828
829 if (value != null && config.isComponentTideDisabled(factoryMethod.getComponent().getName(),
830 componentClasses(factoryMethod.getComponent()), value))
831 add = false;
832 }
833 else {
834 value = Component.getInstance(res.getComponentName(), scopeType);
835
836 if (value != null && config.isComponentTideDisabled(factoryMethod.getComponent().getName(),
837 componentClasses(value), value))
838 add = false;
839 }
840
841 restrict = factoryMethod.getComponent().beanClassHasAnnotation(Restrict.class);
842 }
843 else {
844 Object[] val = lookupInStatefulContexts(res.getComponentName(), scopeType);
845 if (val != null) {
846 value = val[0];
847 scopeType = (ScopeType)val[1];
848
849 if (value != null && config.isComponentTideDisabled(res.getComponentName(),
850 componentClasses(value), value))
851 add = false;
852 }
853 }
854
855 if (add) {
856 Object v0 = null;
857 String propName = null;
858 for (int i = 0; i < path.length; i++) {
859 if (value == null)
860 break;
861 // Use modified Reflections for getter because of a bug in Seam 2.0.0
862 v0 = value;
863 propName = path[i];
864 Method getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
865 value = Reflections.invoke(getter, value);
866 }
867
868 getResultsEval(scopeType).put(res, false);
869
870 if (value instanceof TideDataModel) {
871 // Unwrap value
872 value = ((TideDataModel)value).getWrappedData();
873 }
874 else if (value != null) {
875 if (classGetter != null) {
876 classGetter.initialize(v0, propName, value);
877 if (res.getExpression() != null) {
878 String[] fullPath = res.getExpression().split("\\.");
879 Object v = value;
880 for (int i = path.length; i < fullPath.length; i++) {
881 // Use modified Reflections for getter because of a bug in Seam 2.0.0
882 Method getter = org.granite.util.Reflections.getGetterMethod(v.getClass(), fullPath[i]);
883 v0 = v;
884 v = Reflections.invoke(getter, v);
885 // if (v == null)
886 // break;
887 classGetter.initialize(v0, fullPath[i], v);
888 if (v == null)
889 break;
890 }
891 }
892 }
893 }
894
895 int scope = (scopeType == ScopeType.CONVERSATION ? 2 : (scopeType == ScopeType.SESSION ? 1 : 3));
896
897 resultsMap.add(new ContextUpdate(res.getComponentName(), expr, value, scope, Boolean.TRUE.equals(restrict)));
898 add = false;
899 }
900
901 exprs.add(ex);
902 }
903 catch (IllegalArgumentException e) {
904 log.debug("Evaluate result error for " + res.toString(), e);
905 // GDS-566
906 }
907 catch (Exception e) {
908 throw new RuntimeException("Could not evaluate result expression: " + ex, e);
909 }
910 }
911 }
912
913 me.setValue(Boolean.FALSE);
914 }
915 }
916
917 return resultsMap;
918 }
919
920
921 /**
922 * Implementations of intercepted asynchronous calls
923 * Send asynchronous event to client
924 * @param asyncContext current context (session id)
925 * @param targetComponentName target component name
926 * @param methodName method name
927 * @param paramTypes method argument types
928 * @param params argument values
929 * @return result
930 */
931 public Object invokeAsynchronous(AsyncContext asyncContext, String targetComponentName, Class<?> targetComponentClass, String methodName, Class<?>[] paramTypes, Object[] params) {
932 setAsyncContext(asyncContext);
933
934 // Just another ugly hack: the Seam interceptor has set this variable and we don't want it
935 Contexts.getEventContext().remove("org.jboss.seam.async.AsynchronousIntercepter.REENTRANT");
936
937 Component component = TideInit.lookupComponent(targetComponentName);
938
939 // Forces evaluation of all results if they are related to the called component
940 for (Map.Entry<ContextResult, Boolean> me : getResultsEval(component.getScope()).entrySet()) {
941 if (me.getKey().getComponentName().equals(targetComponentName))
942 me.setValue(Boolean.TRUE);
943 }
944
945 Object target = Component.getInstance(targetComponentName);
946
947 Method method;
948 try {
949 method = target.getClass().getMethod(methodName, paramTypes);
950 }
951 catch (NoSuchMethodException nsme) {
952 throw new IllegalStateException(nsme);
953 }
954
955 Object result = Reflections.invokeAndWrap(method, target, params);
956
957 sendEvent(targetComponentName, targetComponentClass);
958
959 return result;
960 }
961
962
963 @Override
964 protected TidePersistenceManager getTidePersistenceManager(boolean create) {
965 return SeamInitializer.instance().getTidePersistenceManager();
966 }
967
968
969 /**
970 * Search for a named attribute in all contexts, in the
971 * following order: method, event, page, conversation,
972 * session, business process, application.
973 *
974 * @return the first component found, or null
975 */
976 public static Object[] lookupInStatefulContexts(String name, ScopeType scope) {
977 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.METHOD.equals(scope)) && Contexts.isMethodContextActive()) {
978 Object result = Contexts.getMethodContext().get(name);
979 if (result != null)
980 return new Object[] { result, Contexts.getMethodContext().getType() };
981 }
982
983 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.EVENT.equals(scope)) && Contexts.isEventContextActive()) {
984 Object result = Contexts.getEventContext().get(name);
985 if (result != null)
986 return new Object[] { result, Contexts.getEventContext().getType() };
987 }
988
989 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.PAGE.equals(scope)) && Contexts.isPageContextActive()) {
990 Object result = Contexts.getPageContext().get(name);
991 if (result != null)
992 return new Object[] { result, Contexts.getPageContext().getType() };
993 }
994
995 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.CONVERSATION.equals(scope)) && Contexts.isConversationContextActive()) {
996 Object result = Contexts.getConversationContext().get(name);
997 if (result != null)
998 return new Object[] { result, Contexts.getConversationContext().getType() };
999 }
1000
1001 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.SESSION.equals(scope)) && Contexts.isSessionContextActive()) {
1002 Object result = Contexts.getSessionContext().get(name);
1003 if (result != null)
1004 return new Object[] { result, Contexts.getSessionContext().getType() };
1005 }
1006
1007 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.BUSINESS_PROCESS.equals(scope)) && Contexts.isBusinessProcessContextActive()) {
1008 Object result = Contexts.getBusinessProcessContext().get(name);
1009 if (result != null)
1010 return new Object[] { result, Contexts.getBusinessProcessContext().getType() };
1011 }
1012
1013 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.APPLICATION.equals(scope)) && Contexts.isApplicationContextActive()) {
1014 Object result = Contexts.getApplicationContext().get(name);
1015 if (result != null)
1016 return new Object[] { result, Contexts.getApplicationContext().getType() };
1017 }
1018
1019 return ScopeType.UNSPECIFIED.equals(scope) ? null : new Object[] { null, scope };
1020 }
1021 }