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