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