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