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 */ 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.GraniteConfig; 038import org.granite.context.GraniteContext; 039import org.granite.messaging.amf.io.util.ClassGetter; 040import org.granite.messaging.service.ServiceException; 041import org.granite.messaging.service.ServiceInvocationContext; 042import org.granite.messaging.webapp.HttpGraniteContext; 043import org.granite.tide.IInvocationCall; 044import org.granite.tide.IInvocationResult; 045import org.granite.tide.TidePersistenceManager; 046import org.granite.tide.TideServiceContext; 047import org.granite.tide.TideStatusMessages; 048import org.granite.tide.annotations.BypassTideMerge; 049import org.granite.tide.async.AsyncPublisher; 050import org.granite.tide.data.DataContext; 051import org.granite.tide.data.DataMergeContext; 052import org.granite.tide.data.DataUpdatePostprocessor; 053import org.granite.tide.invocation.ContextEvent; 054import org.granite.tide.invocation.ContextResult; 055import org.granite.tide.invocation.ContextUpdate; 056import org.granite.tide.invocation.InvocationCall; 057import org.granite.tide.invocation.InvocationResult; 058import org.granite.tide.seam.async.AsyncContext; 059import org.granite.tide.seam.lazy.SeamInitializer; 060import org.jboss.seam.Component; 061import org.jboss.seam.ScopeType; 062import org.jboss.seam.annotations.Destroy; 063import org.jboss.seam.annotations.Factory; 064import org.jboss.seam.annotations.Logger; 065import org.jboss.seam.annotations.Observer; 066import org.jboss.seam.annotations.datamodel.DataModelSelection; 067import org.jboss.seam.annotations.security.Restrict; 068import org.jboss.seam.contexts.Context; 069import org.jboss.seam.contexts.Contexts; 070import org.jboss.seam.core.Conversation; 071import org.jboss.seam.core.Init.FactoryExpression; 072import org.jboss.seam.core.Init.FactoryMethod; 073import org.jboss.seam.framework.Home; 074import org.jboss.seam.log.Log; 075import org.jboss.seam.security.Identity; 076import org.jboss.seam.util.Reflections; 077 078 079/** 080 * @author William DRAI 081 */ 082public 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}