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 021package org.granite.tide.seam; 022 023import java.lang.reflect.Field; 024import java.lang.reflect.Method; 025import java.lang.reflect.Type; 026import java.util.ArrayList; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032 033import javax.persistence.Entity; 034import javax.servlet.http.HttpSession; 035 036import org.granite.config.GraniteConfig; 037import org.granite.context.GraniteContext; 038import org.granite.messaging.amf.io.util.ClassGetter; 039import org.granite.messaging.service.ServiceException; 040import org.granite.messaging.service.ServiceInvocationContext; 041import org.granite.messaging.webapp.HttpGraniteContext; 042import org.granite.tide.IInvocationCall; 043import org.granite.tide.IInvocationResult; 044import org.granite.tide.TidePersistenceManager; 045import org.granite.tide.TideServiceContext; 046import org.granite.tide.TideStatusMessages; 047import org.granite.tide.annotations.BypassTideMerge; 048import org.granite.tide.async.AsyncPublisher; 049import org.granite.tide.data.DataContext; 050import org.granite.tide.data.DataMergeContext; 051import org.granite.tide.data.DataUpdatePostprocessor; 052import org.granite.tide.invocation.ContextEvent; 053import org.granite.tide.invocation.ContextResult; 054import org.granite.tide.invocation.ContextUpdate; 055import org.granite.tide.invocation.InvocationCall; 056import org.granite.tide.invocation.InvocationResult; 057import org.granite.tide.seam.async.AsyncContext; 058import org.granite.tide.seam.lazy.SeamInitializer; 059import org.jboss.seam.Component; 060import org.jboss.seam.ScopeType; 061import org.jboss.seam.annotations.Destroy; 062import org.jboss.seam.annotations.Factory; 063import org.jboss.seam.annotations.Logger; 064import org.jboss.seam.annotations.Observer; 065import org.jboss.seam.annotations.datamodel.DataModelSelection; 066import org.jboss.seam.annotations.security.Restrict; 067import org.jboss.seam.contexts.Context; 068import org.jboss.seam.contexts.Contexts; 069import org.jboss.seam.core.Conversation; 070import org.jboss.seam.core.Init.FactoryExpression; 071import org.jboss.seam.core.Init.FactoryMethod; 072import org.jboss.seam.framework.Home; 073import org.jboss.seam.log.Log; 074import org.jboss.seam.security.Identity; 075import org.jboss.seam.util.Reflections; 076 077 078/** 079 * @author William DRAI 080 */ 081public 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}