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