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 res = new InvocationResult(result, results);
456 res.setScope(scope);
457 res.setRestrict(restrict);
458 if (component != null) {
459 if (component.beanClassHasAnnotation(BypassTideMerge.class))
460 res.setMerge(false);
461 else {
462 try {
463 if (context != null) {
464 Method m = component.getBeanClass().getMethod(context.getMethod().getName(), context.getMethod().getParameterTypes());
465 if (m.isAnnotationPresent(BypassTideMerge.class))
466 res.setMerge(false);
467 }
468 }
469 catch (Exception e) {
470 log.warn("Could not find bean method", e);
471 }
472 }
473 }
474
475 if (Conversation.instance().isLongRunning()) {
476 // Put results in merge context to keep data in extended persistence context between remote calls
477 DataMergeContext.addResultEntity(result);
478 for (ContextUpdate cu : results)
479 DataMergeContext.addResultEntity(cu.getValue());
480 }
481
482 res.setUpdates(updates);
483
484 // Adds events in result object
485 res.setEvents(tideInvocation.getEvents());
486
487 // Save current set of entities loaded in a conversation scoped component to handle case of extended PM
488 SeamInitializer.instance().saveLoadedEntities();
489
490 // Adds context messages in result object
491 TideStatusMessages statusMessages = getTideMessages();
492 res.setMessages(statusMessages.getMessages());
493 res.setKeyedMessages(statusMessages.getKeyedMessages());
494
495 clearTideMessages();
496
497 // Clean thread
498 TideInvocation.remove();
499
500 return res;
501 }
502
503 /**
504 * Intercepts a fault on the invocation
505 *
506 * @param context invocation context
507 * @param t exception thrown
508 * @param componentName name of the invoked component
509 */
510 @Override
511 public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
512 clearTideMessages();
513
514 // Clean thread: very important to avoid phantom evaluations after exceptions
515 TideInvocation.remove();
516 }
517
518
519 public void addResultEval(ScopedContextResult result) {
520 getResultsEval(result.getScope()).put(result, Boolean.TRUE);
521 }
522
523
524 /**
525 * Evaluate updates in current server context
526 *
527 * @param updates list of updates
528 * @param component the target component
529 * @param target the target instance
530 */
531 public void restoreContext(List<ContextUpdate> updates, Component component, Object target) {
532 if (updates == null)
533 return;
534
535 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
536
537 // Restore context
538 for (ContextUpdate update : updates) {
539
540 log.debug("Before invocation: evaluating expression #0.#1", update.getComponentName(), update.getExpression());
541
542 Component sourceComponent = TideInit.lookupComponent(update.getComponentName());
543 String sourceComponentName = sourceComponent != null ? sourceComponent.getName() : update.getComponentName();
544
545 Object previous = null;
546 if (update.getExpression() != null) {
547 // Set component property
548 // Ignore expression on external components if target component is not interception enabled
549 // to avoid issues with bijection, persistence contexts and transactions
550 if (target != null || (component != null && component.getName().equals(sourceComponentName))) {
551 // Get current values in Seam context
552 String[] path = update.getExpression().split("\\.");
553 Object instance = component != null && component.getName().equals(sourceComponentName) && target != null
554 ? target
555 : Component.getInstance(sourceComponentName);
556 boolean disabled = instance != null && sourceComponent != null &&
557 config.isComponentTideDisabled(sourceComponentName, componentClasses(sourceComponent), instance);
558 if (!disabled) {
559 Object bean = instance;
560 Object value = instance;
561
562 List<Field> dmsFields = instance != null && path.length == 1
563 ? org.granite.util.Reflections.getFields(instance.getClass(), DataModelSelection.class) : null;
564 List<String> dmsFieldNames = null;
565 if (dmsFields != null && !dmsFields.isEmpty()) {
566 dmsFieldNames = new ArrayList<String>(dmsFields.size());
567 for (Field f : dmsFields)
568 dmsFieldNames.add(f.getName());
569 }
570
571 if (update.getValue() != null) {
572 boolean getPrevious = true;
573 if (update.getValue().getClass().getAnnotation(Entity.class) != null) {
574 org.granite.util.Entity entity = new org.granite.util.Entity(update.getValue());
575 if (entity.getIdentifier() == null)
576 getPrevious = false;
577 }
578 if (getPrevious) {
579 try {
580 for (int i = 0; i < path.length; i++) {
581 if (value == null)
582 break;
583 if (i == 0 && dmsFieldNames != null && dmsFieldNames.contains(path[0])) {
584 Field field = org.granite.util.Reflections.getField(value.getClass(), path[i]);
585 field.setAccessible(true);
586 value = Reflections.get(field, value);
587 if (i < path.length-1)
588 bean = value;
589 }
590 else {
591 // Use modified Reflections for getter because of a bug in Seam 2.0.0
592 Method getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
593 value = Reflections.invoke(getter, value);
594 if (i < path.length-1)
595 bean = value;
596 }
597 }
598 }
599 catch (IllegalArgumentException e) {
600 // No getter found to retrieve current value
601 log.warn("Partial merge only: " + e.getMessage());
602 value = null;
603 }
604 catch (Exception e) {
605 throw new ServiceException("Could not get property: " + update.toString(), e);
606 }
607 previous = value;
608 }
609 }
610
611 // Set new value
612 try {
613 if (bean != null && path.length == 1 && dmsFieldNames != null && dmsFieldNames.contains(path[0])) {
614 Field field = org.granite.util.Reflections.getField(bean.getClass(), path[0]);
615 field.setAccessible(true);
616 value = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters().convert(update.getValue(), field.getType());
617 // Merge entities into current persistent context if needed
618 value = mergeExternal(value, previous);
619 Reflections.set(field, bean, value);
620 }
621 else if (bean != null) {
622 Method setter = Reflections.getSetterMethod(bean.getClass(), path[path.length-1]);
623 Type type = setter.getParameterTypes()[0];
624 if (bean instanceof Home<?, ?> && "id".equals(path[path.length-1])) {
625 // Special (ugly ?) handling for Home object to try to guess id type (setId is of type Object)
626 try {
627 Class<?> entityClass = ((Home<?, ?>)bean).getEntityClass();
628 org.granite.util.Entity entity = new org.granite.util.Entity(entityClass);
629 type = entity.getIdentifierType();
630 }
631 catch (Exception e) {
632 // Ignore
633 }
634 }
635 value = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters().convert(update.getValue(), type);
636 // Merge entities into current persistent context if needed
637 value = mergeExternal(value, previous);
638 Reflections.invoke(setter, bean, value);
639 }
640 }
641 catch (Exception e) {
642 throw new ServiceException("Could not restore property: " + update.toString(), e);
643 }
644 }
645 }
646 }
647 else {
648 // Set context variable
649 if (sourceComponent != null) {
650 ScopeType scope = sourceComponent.getScope();
651 if (!((update.getScope() == 2 && (scope == ScopeType.EVENT
652 || scope == ScopeType.STATELESS
653 || scope == ScopeType.CONVERSATION
654 || scope == ScopeType.BUSINESS_PROCESS
655 || scope == ScopeType.METHOD
656 || scope == ScopeType.PAGE))
657 || (update.getScope() == 1 && (scope == ScopeType.EVENT
658 || scope == ScopeType.STATELESS
659 || scope == ScopeType.SESSION
660 || scope == ScopeType.METHOD
661 || scope == ScopeType.PAGE))
662 || (update.getScope() == 3 && (scope == ScopeType.EVENT
663 || scope == ScopeType.STATELESS
664 || scope == ScopeType.METHOD
665 || scope == ScopeType.PAGE)))) {
666 scope = ScopeType.EVENT;
667 }
668
669 previous = scope.getContext().get(sourceComponentName);
670
671 boolean disabled = previous != null && config.isComponentTideDisabled(sourceComponentName, componentClasses(sourceComponent), previous);
672 if (!disabled) {
673 Object value = mergeExternal(update.getValue(), previous);
674
675 scope.getContext().set(sourceComponentName, value);
676 }
677 }
678 else {
679 Object[] prev = lookupInStatefulContexts(sourceComponentName, ScopeType.UNSPECIFIED);
680 ScopeType scope = ScopeType.UNSPECIFIED;
681 if (prev != null) {
682 previous = prev[0];
683 scope = (ScopeType)prev[1];
684 }
685
686 boolean disabled = previous != null && config.isComponentTideDisabled(sourceComponentName,
687 componentClasses(previous), previous);
688 if (!disabled) {
689 if (scope == ScopeType.UNSPECIFIED) {
690 scope = ScopeType.EVENT;
691 scope.getContext().set(sourceComponentName + "_tide_unspecified_", true);
692 }
693
694 Object value = mergeExternal(update.getValue(), previous);
695
696 scope.getContext().set(sourceComponentName, value);
697 }
698 }
699 }
700 }
701 }
702
703
704 private static final ScopeType[] EVAL_SCOPE_TYPES = {
705 ScopeType.UNSPECIFIED,
706 ScopeType.EVENT,
707 ScopeType.CONVERSATION,
708 ScopeType.SESSION,
709 ScopeType.BUSINESS_PROCESS,
710 ScopeType.APPLICATION
711 };
712
713 /**
714 * Evaluate results from context
715 *
716 * @param component the target component
717 * @param target the target instance
718 * @param nothing used by initializer to avoid interactions with context sync
719 *
720 * @return list of updates to send back to the client
721 */
722 public List<ContextUpdate> evaluateResults(Component component, Object target, boolean nothing) {
723
724 List<ContextUpdate> resultsMap = new ArrayList<ContextUpdate>();
725
726 if (nothing)
727 return resultsMap;
728
729 List<String> exprs = new ArrayList<String>();
730 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
731 ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
732
733 SeamInitializer.instance();
734
735 for (ScopeType evalScopeType : EVAL_SCOPE_TYPES) {
736 for (Map.Entry<ContextResult, Boolean> me : getResultsEval(evalScopeType).entrySet()) {
737 if (!me.getValue())
738 continue;
739
740 ContextResult res = me.getKey();
741 Component targetComponent = TideInit.lookupComponent(res.getComponentName());
742
743 String targetComponentName = targetComponent != null ? targetComponent.getName() : res.getComponentName();
744
745 if (res.getExpression() != null && component != null && targetComponent != null && !(component.getName().equals(targetComponentName))) {
746 // Ignore results concerning other components for this time
747 // In this case is has to be a non interception enabled component
748 continue;
749 }
750
751 int idx = 0;
752 boolean add = true;
753
754 // Force evaluation of consecutive properties
755 while (idx >= 0) {
756 idx = res.getExpression() != null ? res.getExpression().indexOf(".", idx+1) : -1;
757
758 String expr = (idx > 0 ? res.getExpression().substring(0, idx) : res.getExpression());
759 String ex = expr != null ? res.getComponentName() + "." + expr : res.getComponentName();
760
761 if (!exprs.contains(ex)) {
762 log.debug("After invocation: evaluating expression #0", ex);
763
764 String[] path = expr != null ? expr.split("\\.") : new String[0];
765
766 try {
767 Object value = null;
768 ScopeType scopeType = res instanceof ScopedContextResult ? ((ScopedContextResult)res).getScope() : ScopeType.UNSPECIFIED;
769 Boolean restrict = res.getRestrict();
770
771 FactoryMethod factoryMethod = null;
772 FactoryExpression factoryExpression = null;
773 if (targetComponent == null) {
774 factoryExpression = TideInit.lookupFactoryExpression(res.getComponentName());
775 if (factoryExpression == null)
776 factoryMethod = TideInit.lookupFactory(res.getComponentName());
777 }
778
779 if (targetComponent != null) {
780 if (component != null && component.getName().equals(targetComponent.getName())) {
781 value = target;
782 scopeType = targetComponent.getScope();
783 }
784 else if (ScopeType.UNSPECIFIED.equals(scopeType)) {
785 value = Component.getInstance(targetComponent.getName());
786 scopeType = targetComponent.getScope();
787 if (ScopeType.STATELESS.equals(scopeType))
788 scopeType = ScopeType.EVENT;
789
790 if (value != null && config.isComponentTideDisabled(targetComponentName,
791 componentClasses(targetComponent), value))
792 add = false;
793 }
794 else {
795 value = Component.getInstance(targetComponent.getName(), scopeType);
796
797 if (value != null && config.isComponentTideDisabled(targetComponentName,
798 componentClasses(value), value))
799 add = false;
800 }
801
802 restrict = targetComponent.beanClassHasAnnotation(Restrict.class);
803 }
804 else if (factoryExpression != null) {
805 String expressionString = factoryExpression.getMethodBinding() != null
806 ? factoryExpression.getMethodBinding().getExpressionString()
807 : factoryExpression.getValueBinding().getExpressionString();
808 int iedx = expressionString.indexOf(".");
809 String expressionBaseName = expressionString.substring(2, iedx);
810
811 if (ScopeType.UNSPECIFIED.equals(scopeType)) {
812 value = Component.getInstance(res.getComponentName());
813 scopeType = factoryExpression.getScope();
814 if (ScopeType.STATELESS.equals(scopeType))
815 scopeType = ScopeType.EVENT;
816
817 if (value != null && config.isComponentTideDisabled(expressionBaseName, componentClasses(value), value))
818 add = false;
819 }
820 else {
821 value = Component.getInstance(res.getComponentName(), scopeType);
822
823 if (value != null && config.isComponentTideDisabled(expressionBaseName, componentClasses(value), value))
824 add = false;
825 }
826
827 Component factoryComponent = TideInit.lookupComponent(expressionBaseName);
828 restrict = factoryComponent != null ? factoryComponent.beanClassHasAnnotation(Restrict.class) : false;
829 }
830 else if (factoryMethod != null) {
831 if (ScopeType.UNSPECIFIED.equals(scopeType)) {
832 value = Component.getInstance(factoryMethod.getMethod().getAnnotation(Factory.class).value());
833 scopeType = factoryMethod.getScope();
834 if (ScopeType.STATELESS.equals(scopeType))
835 scopeType = ScopeType.EVENT;
836
837 if (value != null && config.isComponentTideDisabled(factoryMethod.getComponent().getName(),
838 componentClasses(factoryMethod.getComponent()), value))
839 add = false;
840 }
841 else {
842 value = Component.getInstance(res.getComponentName(), scopeType);
843
844 if (value != null && config.isComponentTideDisabled(factoryMethod.getComponent().getName(),
845 componentClasses(value), value))
846 add = false;
847 }
848
849 restrict = factoryMethod.getComponent().beanClassHasAnnotation(Restrict.class);
850 }
851 else {
852 Object[] val = lookupInStatefulContexts(res.getComponentName(), scopeType);
853 if (val != null) {
854 value = val[0];
855 scopeType = (ScopeType)val[1];
856
857 if (value != null && config.isComponentTideDisabled(res.getComponentName(),
858 componentClasses(value), value))
859 add = false;
860 }
861 }
862
863 if (add) {
864 Object v0 = null;
865 String propName = null;
866 for (int i = 0; i < path.length; i++) {
867 if (value == null)
868 break;
869 // Use modified Reflections for getter because of a bug in Seam 2.0.0
870 v0 = value;
871 propName = path[i];
872 Method getter = null;
873 try {
874 getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
875 }
876 catch (IllegalArgumentException e) {
877 // GDS-566
878 }
879 if (getter != null)
880 value = Reflections.invoke(getter, value);
881 }
882
883 getResultsEval(scopeType).put(res, false);
884
885 if (value instanceof TideDataModel) {
886 // Unwrap value
887 value = ((TideDataModel)value).getWrappedData();
888 }
889 else if (value != null) {
890 if (classGetter != null) {
891 classGetter.initialize(v0, propName, value);
892 if (res.getExpression() != null) {
893 String[] fullPath = res.getExpression().split("\\.");
894 Object v = value;
895 for (int i = path.length; i < fullPath.length; i++) {
896 // Use modified Reflections for getter because of a bug in Seam 2.0.0
897 Method getter = org.granite.util.Reflections.getGetterMethod(v.getClass(), fullPath[i]);
898 v0 = v;
899 v = Reflections.invoke(getter, v);
900 // if (v == null)
901 // break;
902 classGetter.initialize(v0, fullPath[i], v);
903 if (v == null)
904 break;
905 }
906 }
907 }
908 }
909
910 int scope = (scopeType == ScopeType.CONVERSATION ? 2 : (scopeType == ScopeType.SESSION ? 1 : 3));
911
912 resultsMap.add(new ContextUpdate(res.getComponentName(), expr, value, scope, Boolean.TRUE.equals(restrict)));
913 add = false;
914 }
915
916 exprs.add(ex);
917 }
918 catch (Exception e) {
919 throw new ServiceException("Could not evaluate result expression: " + ex, e);
920 }
921 }
922 }
923
924 me.setValue(Boolean.FALSE);
925 }
926 }
927
928 return resultsMap;
929 }
930
931
932 /**
933 * Implementations of intercepted asynchronous calls
934 * Send asynchronous event to client
935 * @param asyncContext current context (session id)
936 * @param targetComponentName target component name
937 * @param methodName method name
938 * @param paramTypes method argument types
939 * @param params argument values
940 * @return result
941 */
942 public Object invokeAsynchronous(AsyncContext asyncContext, String targetComponentName, Class<?> targetComponentClass, String methodName, Class<?>[] paramTypes, Object[] params) {
943 setAsyncContext(asyncContext);
944
945 // Just another ugly hack: the Seam interceptor has set this variable and we don't want it
946 Contexts.getEventContext().remove("org.jboss.seam.async.AsynchronousIntercepter.REENTRANT");
947
948 Component component = TideInit.lookupComponent(targetComponentName);
949
950 // Forces evaluation of all results if they are related to the called component
951 for (Map.Entry<ContextResult, Boolean> me : getResultsEval(component.getScope()).entrySet()) {
952 if (me.getKey().getComponentName().equals(targetComponentName))
953 me.setValue(Boolean.TRUE);
954 }
955
956 Object target = Component.getInstance(targetComponentName);
957
958 Method method;
959 try {
960 method = target.getClass().getMethod(methodName, paramTypes);
961 }
962 catch (NoSuchMethodException nsme) {
963 throw new IllegalStateException(nsme);
964 }
965
966 Object result = Reflections.invokeAndWrap(method, target, params);
967
968 sendEvent(targetComponentName, targetComponentClass);
969
970 return result;
971 }
972
973
974 @Override
975 protected TidePersistenceManager getTidePersistenceManager(boolean create) {
976 return SeamInitializer.instance().getTidePersistenceManager();
977 }
978
979
980 /**
981 * Search for a named attribute in all contexts, in the
982 * following order: method, event, page, conversation,
983 * session, business process, application.
984 *
985 * @return the first component found, or null
986 */
987 public static Object[] lookupInStatefulContexts(String name, ScopeType scope) {
988 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.METHOD.equals(scope)) && Contexts.isMethodContextActive()) {
989 Object result = Contexts.getMethodContext().get(name);
990 if (result != null)
991 return new Object[] { result, Contexts.getMethodContext().getType() };
992 }
993
994 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.EVENT.equals(scope)) && Contexts.isEventContextActive()) {
995 Object result = Contexts.getEventContext().get(name);
996 if (result != null)
997 return new Object[] { result, Contexts.getEventContext().getType() };
998 }
999
1000 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.PAGE.equals(scope)) && Contexts.isPageContextActive()) {
1001 Object result = Contexts.getPageContext().get(name);
1002 if (result != null)
1003 return new Object[] { result, Contexts.getPageContext().getType() };
1004 }
1005
1006 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.CONVERSATION.equals(scope)) && Contexts.isConversationContextActive()) {
1007 Object result = Contexts.getConversationContext().get(name);
1008 if (result != null)
1009 return new Object[] { result, Contexts.getConversationContext().getType() };
1010 }
1011
1012 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.SESSION.equals(scope)) && Contexts.isSessionContextActive()) {
1013 Object result = Contexts.getSessionContext().get(name);
1014 if (result != null)
1015 return new Object[] { result, Contexts.getSessionContext().getType() };
1016 }
1017
1018 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.BUSINESS_PROCESS.equals(scope)) && Contexts.isBusinessProcessContextActive()) {
1019 Object result = Contexts.getBusinessProcessContext().get(name);
1020 if (result != null)
1021 return new Object[] { result, Contexts.getBusinessProcessContext().getType() };
1022 }
1023
1024 if ((ScopeType.UNSPECIFIED.equals(scope) || ScopeType.APPLICATION.equals(scope)) && Contexts.isApplicationContextActive()) {
1025 Object result = Contexts.getApplicationContext().get(name);
1026 if (result != null)
1027 return new Object[] { result, Contexts.getApplicationContext().getType() };
1028 }
1029
1030 return ScopeType.UNSPECIFIED.equals(scope) ? null : new Object[] { null, scope };
1031 }
1032 }