1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 package org.apache.myfaces.orchestra.conversation.spring; 21 22 import java.util.HashMap; 23 import java.util.Map; 24 25 import org.aopalliance.aop.Advice; 26 import org.apache.commons.logging.Log; 27 import org.apache.commons.logging.LogFactory; 28 import org.apache.myfaces.orchestra.conversation.Conversation; 29 import org.apache.myfaces.orchestra.conversation.ConversationAware; 30 import org.apache.myfaces.orchestra.conversation.ConversationBindingEvent; 31 import org.apache.myfaces.orchestra.conversation.ConversationBindingListener; 32 import org.apache.myfaces.orchestra.conversation.ConversationContext; 33 import org.apache.myfaces.orchestra.conversation.ConversationFactory; 34 import org.apache.myfaces.orchestra.conversation.ConversationManager; 35 import org.apache.myfaces.orchestra.conversation.CurrentConversationAdvice; 36 import org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter; 37 import org.apache.myfaces.orchestra.lib.jsf.PortletOrchestraFacesContextFactory; 38 import org.springframework.aop.Advisor; 39 import org.springframework.aop.SpringProxy; 40 import org.springframework.aop.framework.ProxyFactory; 41 import org.springframework.aop.framework.autoproxy.AutoProxyUtils; 42 import org.springframework.aop.scope.ScopedProxyFactoryBean; 43 import org.springframework.beans.BeansException; 44 import org.springframework.beans.factory.BeanFactory; 45 import org.springframework.beans.factory.BeanFactoryAware; 46 import org.springframework.beans.factory.ObjectFactory; 47 import org.springframework.beans.factory.config.BeanDefinition; 48 import org.springframework.beans.factory.config.BeanPostProcessor; 49 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 50 import org.springframework.beans.factory.config.Scope; 51 import org.springframework.context.ApplicationContext; 52 import org.springframework.context.ApplicationContextAware; 53 import org.springframework.context.ConfigurableApplicationContext; 54 55 /** 56 * Abstract basis class for all the Orchestra scopes. 57 * <p> 58 * A scope object has two quite different roles: 59 * <ol> 60 * <li>It handles the lookup of beans in a scope, and creates them if needed</li> 61 * <li>It handles the creation of Conversation objects, using the spring properties 62 * configured on the scope object.</li> 63 * </ol> 64 * <p> 65 * This base class handles item 1 above, and leaves item 2 to a subclass. The 66 * declaration of interface ConversationFactory needs to be on this class, however, 67 * as the createBean method needs to invoke it. 68 */ 69 public abstract class AbstractSpringOrchestraScope implements 70 ConversationFactory, // Orchestra interfaces 71 Scope, BeanFactoryAware, ApplicationContextAware // Spring interfaces 72 { 73 private static final Advice[] NO_ADVICES = new Advice[0]; 74 private static final String POST_PROCESSOR_BEAN_NAME = 75 AbstractSpringOrchestraScope.class.getName() + "_BeanPostProcessor"; 76 77 private final Log log = LogFactory.getLog(AbstractSpringOrchestraScope.class); 78 79 private ConfigurableApplicationContext applicationContext; 80 private Advice[] advices; 81 private boolean autoProxy = true; 82 83 public AbstractSpringOrchestraScope() 84 { 85 } 86 87 /** 88 * The advices (interceptors) which will be applied to the conversation scoped bean. 89 * These are applied whenever a method is invoked on the bean [1]. 90 * <p> 91 * An application's spring configuration uses this method to control what advices are 92 * applied to beans generated from this scope. One commonly applied advice is the 93 * Orchestra persistence interceptor, which ensures that whenever a method on a 94 * conversation-scoped bean is invoked the "global persistence context" is set 95 * to the context for the conversation that bean is in. 96 * <p> 97 * Note [1]: the advices are only applied when the bean is invoked via its proxy. If 98 * invoked via the "this" pointer of the object the interceptors will not run. This 99 * is generally a good thing, as they are not wanted when a method on the bean invokes 100 * another method on the same bean. However it is bad if the bean passes "this" as a 101 * parameter to some other object that makes a callback on it at some later time. In 102 * that case, the bean must take care to pass its proxy to the remote object, not 103 * itself. See method ConversationUtils.getCurrentBean(). 104 */ 105 public void setAdvices(Advice[] advices) 106 { 107 this.advices = advices; 108 } 109 110 /** 111 * @since 1.1 112 */ 113 protected Advice[] getAdvices() 114 { 115 return advices; 116 } 117 118 /** 119 * Configuration for a scope object to control whether "scoped proxy" objects are 120 * automatically wrapped around each conversation bean. 121 * <p> 122 * Automatically applying scope proxies solves a lot of tricky problems with "stale" 123 * beans, and should generally be used. However it does require CGLIB to be present 124 * in the classpath. It also can impact performance in some cases. Where this is a 125 * problem, this flag can turn autoproxying off. Note that the standard spring 126 * aop:scoped-proxy bean can then be used on individual beans to re-enable 127 * proxying for specific beans if desired. 128 * <p> 129 * This defaults to true. 130 * 131 * @since 1.1 132 */ 133 public void setAutoProxy(boolean state) 134 { 135 autoProxy = state; 136 } 137 138 /** 139 * Return the conversation context id. 140 * <p> 141 * Note: This conversationId is something spring requires. It has nothing to do with the Orchestra 142 * conversation id. 143 * <p> 144 * TODO: what does Spring use this for???? 145 */ 146 public String getConversationId() 147 { 148 ConversationManager manager = ConversationManager.getInstance(); 149 ConversationContext ctx = manager.getCurrentConversationContext(); 150 if (ctx != null) 151 { 152 return Long.toString(ctx.getId(), 10); 153 } 154 155 return null; 156 } 157 158 /** 159 * This is invoked by Spring whenever someone calls getBean(name) on a bean-factory 160 * and the bean-definition for that bean has a scope attribute that maps to an 161 * instance of this class. 162 * <p> 163 * In the normal case, this method returns an internally-created proxy object 164 * that fetches the "real" bean every time a method is invoked on the proxy 165 * (see method getRealBean). This does obviously have some performance impact. 166 * However it is necessary when beans from one conversation are referencing beans 167 * from another conversation as the conversation lifetimes are not the same; 168 * without this proxying there are many cases where "stale" references end up 169 * being used. Most references to conversation-scoped objects are made via EL 170 * expressions, and in this case the performance impact is not significant 171 * relative to the overhead of EL. Note that there is one case where this 172 * proxying is not "transparent" to user code: if a proxied object passes a 173 * "this" pointer to a longer-lived object that retains that pointer then 174 * that reference can be "stale", as it points directly to an instance rather 175 * than to the proxy. 176 * <p> 177 * When the Spring aop:scoped-proxy feature is applied to conversation-scoped 178 * beans, then this functionality is disabled as aop:scoped-proxy has a very 179 * similar effect. Therefore, when this method detects that it has been invoked 180 * by a proxy object generated by aop:scoped-proxy then it returns the real 181 * object (see getRealBean) rather than another proxy. Using aop:scoped-proxy 182 * is somewhat less efficient than Orchestra's customised proxying. 183 * <p> 184 * And when the orchestra proxy needs to access the real object, it won't 185 * call this method; instead, getRealBean is called directly. See class 186 * ScopedBeanTargetSource. 187 */ 188 public Object get(String name, ObjectFactory objectFactory) 189 { 190 if (log.isDebugEnabled()) 191 { 192 log.debug("Method get called for bean " + name); 193 } 194 195 if (_SpringUtils.isModifiedBeanName(name)) 196 { 197 // Backwards compatibility with aop:scoped-proxy tag. 198 // 199 // The user must have included an aop:scoped-proxy within the bean definition, 200 // and here the proxy is firing to try to get the underlying bean. In this 201 // case, return a non-proxied instance of the referenced bean. 202 try 203 { 204 String originalBeanName = _SpringUtils.getOriginalBeanName(name); 205 String conversationName = getConversationNameForBean(name); 206 return getRealBean(conversationName, originalBeanName, objectFactory); 207 } 208 catch(RuntimeException e) 209 { 210 log.error("Exception while accessing bean '" + name + "'"); 211 throw e; 212 } 213 } 214 else if (!autoProxy) 215 { 216 String conversationName = getConversationNameForBean(name); 217 return getRealBean(conversationName, name, objectFactory); 218 } 219 else 220 { 221 // A call has been made by the user to the Spring getBean method 222 // (directly, or via an EL expression). Or the bean is being fetched 223 // as part of spring injection into another object. 224 // 225 // In all these cases, just return a proxy. 226 return getProxy(name, objectFactory); 227 } 228 } 229 230 /** 231 * Return a CGLIB-generated proxy class for the beanclass that is 232 * specified by the provided beanName. 233 * <p> 234 * When any method is invoked on this proxy, it invokes method 235 * getRealBean on this same instance in order to locate a proper 236 * instance, then forwards the method call to it. 237 * <p> 238 * There is a separate proxy instance per beandef (shared across all 239 * instances of that bean). This instance is created when first needed, 240 * and cached for reuse. 241 * 242 * @since 1.1 243 */ 244 protected Object getProxy(String beanName, ObjectFactory objectFactory) 245 { 246 if (log.isDebugEnabled()) 247 { 248 log.debug("getProxy called for bean " + beanName); 249 } 250 251 BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName); 252 String conversationName = getConversationNameForBean(beanName); 253 254 // deal with proxies required for multiple conversations. 255 // This is required to make the viewController scope work where proxies are 256 // required for each conversation a bean has been requested. 257 Map proxies = (Map) beanDefinition.getAttribute(ScopedBeanTargetSource.class.getName()); 258 if (proxies == null) 259 { 260 proxies = new HashMap(); 261 beanDefinition.setAttribute(ScopedBeanTargetSource.class.getName(), proxies); 262 } 263 264 Object proxy = proxies.get(conversationName); 265 if (proxy == null) 266 { 267 if (log.isDebugEnabled()) 268 { 269 log.debug("getProxy: creating proxy for " + beanName); 270 } 271 BeanFactory beanFactory = applicationContext.getBeanFactory(); 272 proxy = _SpringUtils.newProxy(this, conversationName, beanName, objectFactory, beanFactory); 273 proxies.put(conversationName, proxy); 274 } 275 276 // Register the proxy in req scope. The first lookup of a variable using an EL expression during a 277 // request will therefore take the "long" path through JSF's VariableResolver and Spring to get here. 278 // But later lookups of this variable in the same request find the proxy directly in the request scope. 279 // The proxy could potentially be placed in the session or app scope, as there is just one instance 280 // for this spring context, and there is normally only one spring context for a webapp. However 281 // using the request scope is almost as efficient and seems safer. 282 // 283 // Note that the framework adapter might not be initialised during the Spring context initialisation 284 // phase (ie instantiation of singletons during startup), so just skip registration in those cases. 285 // 286 // Note also that when a conversation is invalidated, these objects cached in the request are NOT 287 // removed (the conversation management code is not aware that this code hidden deep in the spring 288 // adapters has done this caching). Leaving stale references in the request scope would be a very bad 289 // thing if they were real object references - which is why we do not do this caching when !autoProxy 290 // is set, or when the beandef is marked with the standard spring aop:scoped-proxy [see method 291 // get(String,ObjectFactory)]. However as these proxies always look up their target again, it is safe 292 // to leave them the request; a new bean will still be created if they are dereferenced after the target 293 // conversation is invalidated. 294 FrameworkAdapter fa = FrameworkAdapter.getCurrentInstance(); 295 if (fa != null) 296 { 297 // ORCHESTRA-15 -= Leonardo Uribe =- 298 // If it is inside a portlet using JSR-301 bridge do not add it 299 // because there are portlet containers like liferay that 300 // could scan request attribute map to put in xml form. In that 301 // case, FrameworkAdapter will not be found and an exception is 302 // thrown when resolving its proxy. 303 // It is possible we could have this case too with other portlet 304 // bridge implementation (myfaces 1.1 and so) but since from this 305 // point we should not call for jsf specific code, we only can 306 // check if the current request contains the key "javax.portlet.faces.phase" 307 if (fa.containsRequestAttribute(PortletOrchestraFacesContextFactory.PORTLET_LIFECYCLE_PHASE)) 308 { 309 fa.setRequestAttribute(beanName, proxy); 310 } 311 } 312 313 314 return proxy; 315 } 316 317 /** 318 * Get a real bean instance (not a scoped-proxy). 319 * <p> 320 * The appropriate Conversation is retrieved; if it does not yet exist then 321 * it is created and started. The conversation name is either specified on the 322 * bean-definition via a custom attribute, or defaults to the bean name. 323 * <p> 324 * Then if the bean already exists in the Conversation it is returned. Otherwise 325 * a new instance is created, stored into the Conversation and returned. 326 * <p> 327 * When a bean is created, a proxy is actually created for it which has one or 328 * more AOP "advices" (ie method interceptors). The CurrentConversationAdvice class 329 * is always attached. Note that if the bean definition contains the aop:proxy 330 * tag (and most do) then the bean that spring creates is already a proxy, ie 331 * what is returned is a proxy of a proxy. 332 * 333 * @param conversationName 334 * @param beanName is the key within the conversation of the bean we are interested in. 335 * 336 * @since 1.1 337 */ 338 protected Object getRealBean(String conversationName, String beanName, ObjectFactory objectFactory) 339 { 340 if (log.isDebugEnabled()) 341 { 342 log.debug("getRealBean called for bean " + beanName); 343 } 344 ConversationManager manager = ConversationManager.getInstance(); 345 Conversation conversation; 346 347 // check if we have a conversation 348 synchronized(manager) 349 { 350 conversation = manager.getConversation(conversationName); 351 if (conversation == null) 352 { 353 // Start the conversation. This eventually results in a 354 // callback to the createConversation method on this class. 355 conversation = manager.startConversation(conversationName, this); 356 } 357 else 358 { 359 // sanity check: verify that two beans with the different scopes 360 // do not declare the same conversationName. 361 assertSameScope(beanName, conversation); 362 } 363 } 364 365 // get the conversation 366 notifyAccessConversation(conversation); 367 synchronized(conversation) 368 { 369 if (!conversation.hasAttribute(beanName)) 370 { 371 Object value; 372 373 // Set the magic property that forces all proxies of this bean to be CGLIB proxies. 374 // It doesn't matter if we do this multiple times.. 375 BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName); 376 beanDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); 377 378 try 379 { 380 // Create the new bean. Note that this will run the 381 // OrchestraAdvisorBeanPostProcessor processor, which 382 // will cause the returned object to actually be a proxy 383 // with the CurrentConversationAdvice (at least) attached to it. 384 value = objectFactory.getObject(); 385 } 386 catch(org.springframework.aop.framework.AopConfigException e) 387 { 388 throw new IllegalStateException( 389 "Unable to create Orchestra proxy" 390 + " for bean " + beanName, e); 391 } 392 393 conversation.setAttribute(beanName, value); 394 395 if (value instanceof ConversationAware) 396 { 397 ((ConversationAware) value).setConversation(conversation); 398 } 399 } 400 } 401 402 // get the bean 403 return conversation.getAttribute(beanName); 404 } 405 406 /** 407 * Verify that the specified conversation was created by this scope object. 408 * 409 * @param beanName is just used when generating an error message on failure. 410 * @param conversation is the conversation to validate. 411 */ 412 protected void assertSameScope(String beanName, Conversation conversation) 413 { 414 // Check that the conversation's factory is this one. 415 // 416 // This handles the case where two different beans declare themselves 417 // as belonging to the same named conversation but with different scope 418 // objects. Allowing that would be nasty, as the conversation 419 // properties (eg lifetime of access or manual) would depend upon which 420 // bean got created first; some other ConversationFactory would have 421 // created the conversation using its configured properties then 422 // we are now adding to that conversation a bean that really wants 423 // the conversation properties defined on this ConversationFactory. 424 // 425 // Ideally the conversation properties would be defined using 426 // the conversation name, not the scope name; this problem would 427 // then not exist. However that would lead to some fairly clumsy 428 // configuration, particularly where lots of beans without explicit 429 // conversationName attributes are used. 430 431 if (conversation.getFactory() != this) 432 { 433 throw new IllegalArgumentException( 434 "Inconsistent scope and conversation name detected for bean " 435 + beanName); 436 } 437 } 438 439 protected void notifyAccessConversation(Conversation conversation) 440 { 441 } 442 443 /** 444 * Invoked by Spring to notify this object of the BeanFactory it is associated with. 445 * <p> 446 * This method is redundant as we also have setApplicationContext. However as this 447 * method (and the BeanFactoryAware interface on this class) were present in release 448 * 1.0 this needs to be kept for backwards compatibility. 449 */ 450 public void setBeanFactory(BeanFactory beanFactory) throws BeansException 451 { 452 } 453 454 /** 455 * Register any BeanPostProcessors that are needed by this scope. 456 * <p> 457 * This is an alternative to requiring users to also add an orchestra BeanPostProcessor element 458 * to their xml configuration file manually. 459 * <p> 460 * When a bean <i>instance</i> is created by Spring, it always runs every single BeanPostProcessor 461 * that has been registered with it. 462 * 463 * @since 1.1 464 */ 465 public void defineBeanPostProcessors(ConfigurableListableBeanFactory cbf) throws BeansException 466 { 467 if (!cbf.containsSingleton(POST_PROCESSOR_BEAN_NAME)) 468 { 469 BeanPostProcessor processor = new OrchestraAdvisorBeanPostProcessor(applicationContext); 470 471 // Adding the bean to the singletons set causes it to be picked up by the standard 472 // AbstractApplicationContext.RegisterBeanPostProcessors method; that calls 473 // getBeanNamesForType(BeanPostProcessor.class, ...) which finds stuff in the 474 // singleton map even when there is no actual BeanDefinition for it. 475 // 476 // We cannot call cbf.addBeanPostProcessor even if we want to, as the singleton 477 // registration will be added again, making the processor run twice on each bean. 478 // And we need the singleton registration in order to avoid registering this once 479 // for each scope object defined in spring. 480 cbf.registerSingleton(POST_PROCESSOR_BEAN_NAME, processor); 481 } 482 } 483 484 /** 485 * Get the conversation for the given beanName. 486 * Returns null if the conversation does not exist. 487 */ 488 protected Conversation getConversationForBean(String beanDefName) 489 { 490 ConversationManager manager = ConversationManager.getInstance(); 491 String conversationName = getConversationNameForBean(beanDefName); 492 Conversation conversation = manager.getConversation(conversationName); 493 return conversation; 494 } 495 496 /** 497 * Get the conversation-name for bean instances created using the specified 498 * bean definition. 499 */ 500 public String getConversationNameForBean(String beanName) 501 { 502 if (applicationContext == null) 503 { 504 throw new IllegalStateException("Null application context"); 505 } 506 507 // Look up the definition with the specified name. 508 BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName); 509 510 if (ScopedProxyFactoryBean.class.getName().equals(beanDefinition.getBeanClassName())) 511 { 512 // Handle unusual case. 513 // 514 // The user bean must have been defined like this: 515 // <bean name="foo" class="example.Foo"> 516 // <....> 517 // <aop:scopedProxy/> 518 // </bean> 519 // In this case, Spring's ScopedProxyUtils class creates two BeanDefinition objects, one 520 // with name "foo" that creates a proxy object, and one with name "scopedTarget.foo" 521 // that actually defines the bean of type example.Foo. 522 // 523 // So what we do here is find the renamed bean definition and look there. 524 // 525 // This case does not occur when this method is invoked from within this class; the 526 // spring scope-related callbacks always deal with the beandef that is scoped to 527 // this scope - which is the original (though renamed) beandef. 528 beanName = _SpringUtils.getModifiedBeanName(beanName); 529 beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName); // NON-NLS 530 } 531 532 String convName = getExplicitConversationName(beanDefinition); 533 if (convName == null) 534 { 535 // The beanname might have been of form "scopedTarget.foo" (esp from registerDestructionCallback). 536 // But in this case, the conversation name will just be "foo", so strip the prefix off. 537 // 538 // Note that this does happen quite often for calls from within this class when aop:scoped-proxy 539 // is being used (which is not recommended but is supported). 540 convName = _SpringUtils.getOriginalBeanName(beanName); 541 } 542 return convName; 543 } 544 545 /** 546 * Return the explicit conversation name for this bean definition, or null if there is none. 547 * <p> 548 * This is a separate method so that subclasses can determine conversation names via alternate methods. 549 * In particular, a subclass may want to look for an annotation on the class specified by the definition. 550 * 551 * @since 1.1 552 */ 553 protected String getExplicitConversationName(BeanDefinition beanDef) 554 { 555 String attr = (String) beanDef.getAttribute( 556 BeanDefinitionConversationNameAttrDecorator.CONVERSATION_NAME_ATTRIBUTE); 557 return attr; 558 } 559 560 /** 561 * Strip off any Spring namespace (eg scopedTarget). 562 * <p> 563 * This method will simply strip off anything before the first dot. 564 * 565 * @deprecated Should not be necessary in user code. 566 */ 567 protected String buildBeanName(String name) 568 { 569 if (name == null) 570 { 571 return null; 572 } 573 574 int pos = name.indexOf('.'); 575 if (pos < 0) 576 { 577 return name; 578 } 579 580 return name.substring(pos + 1); 581 } 582 583 public Object remove(String name) 584 { 585 throw new UnsupportedOperationException(); 586 } 587 588 /** 589 * Add the given runnable wrapped within an 590 * {@link org.apache.myfaces.orchestra.conversation.ConversationBindingListener} to 591 * the conversation map. 592 * <p> 593 * This ensures it will be called during conversation destroy. 594 * <p> 595 * Spring calls this method whenever a bean in this scope is created, if that bean 596 * has a "destroy method". Note however that it appears that it can also call it even 597 * for beans that do not have a destroy method when there is a "destruction aware" 598 * BeanPostProcessor attached to the context (spring version 2.5 at least). 599 * <p> 600 * When an aop:scoped-proxy has been used inside the bean, then the "new" definition 601 * does not have any scope attribute, so orchestra is not invoked for it. However 602 * the "renamed" bean does, and so this is called. 603 */ 604 public void registerDestructionCallback(String name, final Runnable runnable) 605 { 606 if (log.isDebugEnabled()) 607 { 608 log.debug("registerDestructionCallback for [" + name + "]"); 609 } 610 611 Conversation conversation = getConversationForBean(name); 612 if (conversation == null) 613 { 614 // This should never happen because this should only be called after the bean 615 // instance has been created via scope.getBean, which always creates the 616 // conversation for the bean. 617 throw new IllegalStateException("No conversation for bean [" + name + "]"); 618 } 619 if (runnable == null) 620 { 621 throw new IllegalStateException("No runnable object for bean [" + name + "]"); 622 } 623 624 // Add an object to the conversation as a bean so that when the conversation is removed 625 // its valueUnbound method will be called. However we never need to retrieve this object 626 // from the context by name, so use a totally unique name as the bean key. 627 conversation.setAttribute( 628 runnable.getClass().getName() + "@" + System.identityHashCode(runnable), 629 new ConversationBindingListener() 630 { 631 public void valueBound(ConversationBindingEvent event) 632 { 633 } 634 635 public void valueUnbound(ConversationBindingEvent event) 636 { 637 runnable.run(); 638 } 639 } 640 ); 641 } 642 643 /** 644 * Get an ApplicationContext injected by Spring. See ApplicationContextAware interface. 645 */ 646 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 647 { 648 if (!(applicationContext instanceof ConfigurableApplicationContext)) 649 { 650 throw new IllegalArgumentException("a ConfigurableApplicationContext is required"); 651 } 652 653 this.applicationContext = (ConfigurableApplicationContext) applicationContext; 654 defineBeanPostProcessors(this.applicationContext.getBeanFactory()); 655 } 656 657 /** 658 * @since 1.2 659 */ 660 protected ConfigurableApplicationContext getApplicationContext() 661 { 662 return applicationContext; 663 } 664 665 /** 666 * Return the Advisors that should be applied to beans associated with this scope object. 667 * <p> 668 * Note that logically Advisors are associated with a Conversation. It is an implementation 669 * artifact of the Spring implementation of Orchestra that we use a spring Scope object to 670 * hold the advices; theoretically with other dependency-injection frameworks we could 671 * configure things differently. 672 */ 673 Advisor[] getAdvisors(Conversation conversation, String beanName) 674 { 675 Advice[] advices = this.getAdvices(); 676 if ((advices == null) || (advices.length == 0)) 677 { 678 advices = NO_ADVICES; 679 } 680 681 // wrap every Advice in an Advisor instance that returns it in all cases 682 int len = advices.length + 1; 683 Advisor[] advisors = new Advisor[len]; 684 685 // always add the standard CurrentConversationAdvice, and do it FIRST, so it runs first 686 Advice currConvAdvice = new CurrentConversationAdvice(conversation, beanName); 687 advisors[0] = new SimpleAdvisor(currConvAdvice); 688 for(int i=0; i<advices.length; ++i) 689 { 690 advisors[i+1] = new SimpleAdvisor(advices[i]); 691 } 692 693 return advisors; 694 } 695 696 /** 697 * Return a proxy object that "enters" the specified conversation before forwarding the 698 * method call on to the specified instance. 699 * <p> 700 * Entering the conversation means running all the Advices associated with the conversation. 701 * The specified conversation object is assumed to use this Scope object. 702 */ 703 Object createProxyFor(Conversation conversation, Object instance) 704 { 705 if (instance instanceof SpringProxy) 706 { 707 // This is already a proxy, so don't wrap it again. Doing this check means that 708 // user code can safely write things like 709 // return ConversationUtils.bindToCurrent(this) 710 // without worrying about whether "this" is a spring bean marked as conversation-scoped 711 // or not. Requiring beans to know about the configuration setup is bad practice. 712 // 713 // Ideally we would check here that this instance is indeed a proxy for the 714 // specified conversation and throw an exception. However that is just a 715 // nice-to-have. 716 return instance; 717 } 718 719 // The currentConversationAdvice constructor requires a beanName parameter. As the 720 // instance we are wrapping here is usually not defined in the dependency-injection 721 // framework configuration at all, we have to invent a dummy name here. 722 // 723 // The beanName affects ConversationUtils methods getCurrentBean and invalidateAndRestartCurrent. 724 // Neither should ever be called by beans artificially wrapped in a proxy like this, so any old 725 // "bean name" will do. Including the class-name of the bean we wrap seems helpful here.. 726 String beanName = "dummy$" + instance.getClass().getName(); 727 728 ProxyFactory proxyFactory = new ProxyFactory(instance); 729 proxyFactory.setProxyTargetClass(true); 730 Advisor[] advisors = getAdvisors(conversation, beanName); 731 for(int i=0; i<advisors.length; ++i) 732 { 733 proxyFactory.addAdvisor(advisors[i]); 734 } 735 736 proxyFactory.addInterface(SpringProxy.class); 737 return proxyFactory.getProxy(instance.getClass().getClassLoader()); 738 } 739 }