10 Replies Latest reply: Nov 10, 2010 7:56 AM by 811610 RSS

    Using  spring beans with extensibleEnvironment

    670511
      Hi, I'm trying to let the extensible environment use a couple of spring beans.

      There's documentation about using spring beans in coherence described in http://coherence.oracle.com/display/COH35UG/Integrate+CacheFactory+with+Spring but this is not compatible with the common's extensible environment.

      I thought about creating a new NameSpaceHandler that is BeanFactoryAware and that would get the beans from the beanFactory like the following:
      public class SpringNameSpaceContentHandler implements NamespaceContentHandler, ElementContentHandler, BeanFactoryAware
      {
      
           private static final Logger logger = LoggerFactory.getLogger( SpringNameSpaceContentHandler.class );
      
           /**
            * Spring BeanFactory used by this CacheFactory
            */
           private BeanFactory m_beanFactory;
      
           /**
            * Prefix used in cache configuration "class-name" element to indicate this bean is in Spring
            */
           private static final String SPRING_BEAN = "spring:ref";
      
           /**
            * {@inheritDoc}
            */
           public Object onElement( ConfigurationContext context, XmlElement element )
           {
                if ( element.getName().equals( SPRING_BEAN ) )
                {
                     try
                     {
                          return parseInstance( context, element );
                     }
                     catch ( Exception e )
                     {
                          throw Base.ensureRuntimeException( e );
                     }
                }
      
                return null;
           }
      
           /**
            * {@inheritDoc}
            */
           public void onEndScope( ConfigurationContext arg0, URI arg1 )
           {
           }
      
           /**
            * {@inheritDoc}
            */
           public void onStartScope( ConfigurationContext arg0, URI arg1 )
           {
           }
      
           /**
            * Parse the spring bean element.
            * 
            * @param context
            *            the {@link ConfigurationContext} to use
            * @param element
            *            the {@link XmlElement} containing the config element
            * 
            * @return the created instance
            * 
            */
           private Object parseInstance( ConfigurationContext context, XmlElement element )
           {
                QualifiedName elementName = new QualifiedName( element.getName() );
                if ( elementName.getLocalName().equals( "ref" ) )
                {
                     XmlValue value = element.getAttribute( "bean" );
                     String beanName = value.getString();
      
                     logger.debug("Instantiating {}", beanName);
      
                     Assert.isTrue( beanName != null && beanName.length() > 0, "Bean name required");
                     Object oBean = getBeanFactory().getBean(beanName);
                     return oBean;
                }
                return null;
      
           }
      
           /**
            * Get the Spring BeanFactory used by this CacheFactory
            * 
            * @return the Spring {@link BeanFactory} used by this CacheFactory
            */
           private BeanFactory getBeanFactory()
           {
                Assert.notNull( m_beanFactory, "Spring BeanFactory == null" );
                return m_beanFactory;
           }
      
           /**
            * Set the Spring BeanFactory used by this CacheFactory
            * 
            * @param beanFactory
            *            the Spring {@link BeanFactory} used by this CacheFactory
            */
           public void setBeanFactory( BeanFactory beanFactory )
           {
                m_beanFactory = beanFactory;
           }
      This way spring should create the SpringNameSpaceContentHandler injecting its beanFactory which is later used to get a reference to the spring bean when the coherence config file is parsed.

      However this will not work because the ExtensibleEnvironment will create its own instance of SpringNameSpaceContentHandler through the use of DefaultConfigurationContext. The new instance of SpringNameSpaceContentHandler will not contain the beanFactory so that won't be usable.

      What would be the best way to do this?

      Jan
        • 1. Re: Using  spring beans with extensibleEnvironment
          670511
          Ok, silly of me. I just make the SpringNameSpaceContentHandler @Configurable. This way, the application context will be initialized after creation.

          However this will not work either because <class-name> inside the <class-scheme> has to contain the class name to instantiate. So if I do:
          <class-scheme>
               <scheme-name>eventStoreBackStore</scheme-name>
               <class-name>
                    <spring:ref bean="eventMessageCacheStore"/>
               </class-name>
          </class-scheme>
          I'll get
          08 Mar 2010 10:39:27,281 [Logger@9257955 3.5.3/465] ERROR Coherence  - 3.5.3/465 (thread=DistributedCache:EventsBackService, member=1): 
          java.lang.IllegalArgumentException: Class name is missing:
          <class-scheme>
            <scheme-name>eventStoreBackStore</scheme-name>
            <class-name>
              <spring:ref bean='eventMessageCacheStore'/>
            </class-name>
          </class-scheme>
               at com.tangosol.run.xml.XmlHelper.createInstance(XmlHelper.java:2266)
               at com.tangosol.net.DefaultConfigurableCacheFactory.instantiateAny(DefaultConfigurableCacheFactory.java:2994)
               at com.tangosol.net.DefaultConfigurableCacheFactory.instantiateCacheStore(DefaultConfigurableCacheFactory.java:2857)
               at com.tangosol.net.DefaultConfigurableCacheFactory.instantiateReadWriteBackingMap(DefaultConfigurableCacheFactory.java:1493)
               at com.tangosol.net.DefaultConfigurableCacheFactory.configureBackingMap(DefaultConfigurableCacheFactory.java:1258)
               at com.tangosol.net.DefaultConfigurableCacheFactory$Manager.instantiateBackingMap(DefaultConfigurableCacheFactory.java:3492)
               at com.tangosol.coherence.component.util.daemon.queueProcessor.service.grid.DistributedCache$Storage.instantiateResourceMap(DistributedCache.CDB:22)
               at com.tangosol.coherence.component.util.daemon.queueProcessor.service.grid.DistributedCache$Storage.setCacheName(DistributedCache.CDB:27)
               at com.tangosol.coherence.component.util.daemon.queueProcessor.service.grid.DistributedCache$ConfigListener.entryInserted(DistributedCache.CDB:15)
               at com.tangosol.util.MapEvent.dispatch(MapEvent.java:266)
               at com.tangosol.util.MapEvent.dispatch(MapEvent.java:226)
               at com.tangosol.util.MapListenerSupport.fireEvent(MapListenerSupport.java:556)
               at com.tangosol.util.ObservableHashMap.dispatchEvent(ObservableHashMap.java:229)
               at com.tangosol.util.ObservableHashMap$Entry.onAdd(ObservableHashMap.java:270)
               at com.tangosol.util.SafeHashMap.put(SafeHashMap.java:244)
               at com.tangosol.coherence.component.util.collections.WrapperMap.put(WrapperMap.CDB:1)
               at com.tangosol.coherence.component.util.daemon.queueProcessor.service.Grid$ServiceConfigMap.put(Grid.CDB:31)
               at com.tangosol.coherence.component.util.daemon.queueProcessor.service.grid.DistributedCache$StorageIdRequest.onReceived(DistributedCache.CDB:45)
               at com.tangosol.coherence.component.util.daemon.queueProcessor.service.Grid.onMessage(Grid.CDB:9)
               at com.tangosol.coherence.component.util.daemon.queueProcessor.service.Grid.onNotify(Grid.CDB:136)
               at com.tangosol.coherence.component.util.daemon.queueProcessor.service.grid.DistributedCache.onNotify(DistributedCache.CDB:3)
               at com.tangosol.coherence.component.util.Daemon.run(Daemon.CDB:42)
               at java.lang.Thread.run(Thread.java:619)
          I might subclass the ExtensibleEnvironment class to make it interpret spring beans like it is done in the SpringDataStoreAwareCacheFactory. However it would mean the ExtensibleEnvironment isn't that extensible after all.

          Another way to go is to use the following config:
          <class-scheme>
               <scheme-name>eventStoreBackStore</scheme-name>
               <class-factory-name>MySpringBeanFinder</class-factory-name>
               <method-name>getBean</method-name>
          </class-scheme>
          Where the MySpringBeanFinder contains a static reference to the application context.

          A bit messy isn't it? Are there other ways to use spring beans inside coherence using the ExtensibleEnvironment?

          Jan
          • 2. Re: Using  spring beans with extensibleEnvironment
            Brian Oliver
            Basically the problem is here:
            <class-scheme>
                 <scheme-name>eventStoreBackStore</scheme-name>
                 <class-name>
                      <spring:ref bean="eventMessageCacheStore"/>
                 </class-name>
            </class-scheme>
            Essentially the default namespace content handler, which is the CoherenceNamespaceContentHandler in coherence-common-1.6.0.jar, simply captures Coherence configuration elements and passes those onto the DefaultConfigurableCacheFactory (DCCF) for regular processing (note: the ExtensibleEnvironment is a sub-class of the DCCF).

            Consequently when the DCCF sees your cache configuration it doesn't know how to handle the
            <spring:ref bean="eventMessageCacheStore"/>
            element, and hence your runtime error.

            To resolve this what you need to do is override the instantiateAny(...) method of the ExtensibleEnvironment class (in a manner similar to the SpringDataStoreAwareCacheFactory) so that it correctly handles the post-configuration processing of the
            <class-scheme>
            when your cache is instantiated/referenced.

            Hope this helps.

            -- Brian

            PS: We're planning on making the instantiateAny(...) method of the ExtensibleEnvironment class understand namespaces in the next major release. This will mean you can plug in any namespace handler, to support any container, including Spring, directly into the Coherence configuration.

            --------------------------------------------------------
            Brian Oliver | Architect | Oracle Coherence Engineering
            Oracle Fusion Middleware
            • 3. Re: Using  spring beans with extensibleEnvironment
              670511
              Thanks Brian,

              that would indeed do the trick. It's ugly, but it works. Looking forward to newer versions of ExtensibleEnvironment that are able to handle this.

              Jan
              • 4. Re: Using spring beans with extensibleEnvironment
                Brian Oliver
                Hi Jan,

                If you have any further ideas, please don't hesitate in making requests - especially for NamespaceContentHandlers that you'd like to see in a shipped package.

                Regards

                -- Brian
                --------------------------------------------------------------------------
                Brian Oliver | Architect | Oracle Coherence Engineering
                Oracle Fusion Middleware
                • 5. Re: Using  spring beans with extensibleEnvironment
                  Brian Oliver
                  Hi Jan,

                  It is now possible to use custom namespaces within standard Coherence <class-scheme>s.

                  eg:
                  <class-scheme>
                      <instance:class classname="MyClass"/>
                  </class-scheme>
                  Further you could now create your own namespace handler that returns an instance of a ClassScheme (a new interface in Commons) that will be realized/referenced by Coherence when required.

                  -- Brian

                  --------------------------------------------------------
                  Brian Oliver | Architect | Oracle Coherence Engineering
                  Oracle Fusion Middleware
                  • 6. Re: Using  spring beans with extensibleEnvironment
                    670511
                    Great!

                    I created a SpringNameSpaceContentHandler:
                    public class SpringNameSpaceContentHandler extends AbstractNamespaceContentHandler
                    {
                    
                         public SpringNameSpaceContentHandler()
                         {
                              super();
                    
                              registerContentHandler("bean", new ElementContentHandler()
                              {
                    
                                   public Object onElement(ConfigurationContext context,
                                        QualifiedName qualifiedName,
                                        XmlElement xmlElement) throws ConfigurationException
                                        {
                    
                                        // find the beanname for the bean to reference
                                        if (xmlElement.getAttributeMap().containsKey("ref"))
                                        {
                                             XmlValue value = xmlElement.getAttribute("ref");
                                             final String beanName = value.getString();
                                             return new SpringClassScheme(beanName);
                                        }
                                        else
                                        {
                                             throw new ConfigurationException(String.format(
                                                  "The SpringNameSpaceContentHandler expected a 'ref' attribute in the element [%s].",
                                                  xmlElement), "Please add the correct ref attribute.");
                                        }
                                        }
                              });     }
                    
                    
                         @Configurable
                         public static final class SpringClassScheme<T> implements ClassScheme<T>, ExternalizableLite, ApplicationContextAware
                         {
                              private String m_beanName;
                    
                              private transient ApplicationContext m_springContext;
                    
                              @ClientOnly
                              public SpringClassScheme()
                              {
                                   super();
                              }
                    
                              public SpringClassScheme( String beanName )
                              {
                                   m_beanName = beanName;
                              }
                    
                              @Override
                              public T realize( Environment environment, ClassLoader classLoader )
                              {
                                   return (T) m_springContext.getBean( m_beanName );
                              }
                    
                              @Override
                              public boolean realizesClassOf( Class<?> clazz )
                              {
                                   return m_springContext.isTypeMatch( m_beanName, clazz );
                              }
                    
                              @Override
                              public void readExternal( DataInput in ) throws IOException
                              {
                                   m_beanName = ExternalizableHelper.readSafeUTF( in );
                              }
                    
                              @Override
                              public void writeExternal( DataOutput out ) throws IOException
                              {
                                   ExternalizableHelper.writeSafeUTF( out, m_beanName );
                              }
                    
                              @Override
                              public void setApplicationContext( ApplicationContext springContext ) throws BeansException
                              {
                                   m_springContext = springContext;
                              }
                    
                         }
                    }
                    and with this I can use spring beans in my config like the following:
                    <class-scheme>
                            <spring:bean ref="dataStorageCacheStore"/>
                    </class-scheme>
                    Jan
                    • 7. Re: Using  spring beans with extensibleEnvironment
                      Brian Oliver
                      Hi Jan,

                      Totally awesome! I'm glad it worked. Looking forward to seeing what you do next with Namespaces.

                      Regards

                      -- Brian

                      -----
                      Brian Oliver | Architect | Oracle Coherence 
Oracle Fusion Middleware Product Management
                      • 8. Re: Using  spring beans with extensibleEnvironment
                        811610
                        Hi,

                        I've been trying to use the code sample on this page using Coherence 3.6 and Coherence Commons 1.7.2 on JDK 1.6 Update 18. I see how the onElement() method of SpringNameSpaceContentHandler gets invoked, but the ApplicationContext field in the SpringClassScheme instance the handler creates doesn't get set.

                        I already tried registering my ApplicationContext via the following code, but that didn't do the trick yet:
                        Environment environment = (Environment) CacheFactory.getConfigurableCacheFactory();
                        environment.registerResource(ApplicationContext.class, applicationContext);

                        How can I pass my ApplicationContext instance into the ExtensibleEnvironment framework so it gets passed on to the SpringNameSpaceContentHandler and is available to the SpringClassScheme?

                        Thanks
                        • 9. Re: Using  spring beans with extensibleEnvironment
                          Brian Oliver
                          Ok. I think you're almost there.

                          If you've already set your ApplicationContext.class in the Environment, then from you SpringClassScheme.realize method you can simply do the following;
                          return environment.getResource(ApplicationContext.class).getBean(m_beanName);
                          Hope this helps.

                          -- Brian

                          -----
                          Brian Oliver | Architect | Oracle Coherence 

                          Oracle Fusion Middleware Product Management

                          Edited by: Brian Oliver on Nov 9, 2010 12:51 PM
                          • 10. Re: Using  spring beans with extensibleEnvironment
                            811610
                            Hi Brian,

                            thanks for your quick reply. It works now after I applied your fix.

                            Thanks
                            Christian