Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Custom DataSource in combination with Spring's Depencendy Injection

    Hello, I am working on a custom DataSource, extending BasicDataSource, for connecting to Twitter. I would like to wire some dependencies using the Spring Dependency Injection princple. When I do this, however, this generates an error, upon starting the hostedmode.

    I am using SmartGWT Power, nightly build 13-12-2010.

    Wiring up my TwitterDataSource bean with a reference bean, in applicationContext.xml, Spring style
    Code:
     <!--  custom twitter datasource -->  
     <beans:bean id="twitterDataSource" class="com.mypackage.server.customdatasources.TwitterDataSource"> 
        <beans:property name="keysContainer" ref="twitterKeys"/> 
      </beans:bean>
    (if I remove the code above, the error below is not appearing)

    The error I am getting:
    Code:
    === 2010-12-20 13:17:23,548 [ad-0] INFO  ISCInit - Isomorphic SmartClient Framework - Initializing
    === 2010-12-20 13:17:23,586 [ad-0] INFO  ConfigLoader - Attempting to load framework.properties from CLASSPATH
    === 2010-12-20 13:17:23,644 [ad-0] INFO  ConfigLoader - Successfully loaded framework.properties from CLASSPATH at location: jar:file:/Users/marten/Work/EclipseWorkspaces/SocialDipping/war/WEB-INF/lib/isomorphic_core_rpc.jar!/framework.properties
    === 2010-12-20 13:17:23,644 [ad-0] INFO  ConfigLoader - Attempting to load project.properties from CLASSPATH
    === 2010-12-20 13:17:23,646 [ad-0] INFO  ConfigLoader - Unable to locate project.properties in CLASSPATH
    === 2010-12-20 13:17:23,649 [ad-0] INFO  ConfigLoader - Successfully loaded isc_interfaces.properties from CLASSPATH at location: jar:file:/Users/marten/Work/EclipseWorkspaces/SocialDipping/war/WEB-INF/lib/isomorphic_core_rpc.jar!/isc_interfaces.properties
    === 2010-12-20 13:17:23,649 [ad-0] INFO  ConfigLoader - Attempting to load server.properties from CLASSPATH
    === 2010-12-20 13:17:23,651 [ad-0] INFO  ConfigLoader - Successfully loaded server.properties from CLASSPATH at location: file:/Users/marten/Work/EclipseWorkspaces/SocialDipping/war/WEB-INF/classes/server.properties
    === 2010-12-20 13:17:23,656 [ad-0] INFO  Logger - Logging system started.
    === 2010-12-20 13:17:23,656 [ad-0] INFO  ISCInit - Isomorphic SmartClient Framework (SC_SNAPSHOT-2010-12-13/PowerEdition Deployment 2010-12-13) - Initialization Complete
    === 2010-12-20 13:17:23,657 [ad-0] INFO  ISCInit - No ServletContext available yet - using container IO for now
    === 2010-12-20 13:17:23,676 [ad-0] ERROR ISCFile - Configured for containerIO, but servletContext not available!
    Problem loading builtinTypes.xml
    Exception when loading from __USE_CONTAINER__/generatedcode/sc/system/schema/builtinTypes.xml:
    java.lang.NullPointerException
    	at com.isomorphic.io.ISCFile.<init>(ISCFile.java:145)
    	at com.isomorphic.store.ProcessedFileCache.getObjectFromFile(ProcessedFileCache.java:135)
    	at com.isomorphic.xml.XML.getXMLDocument(XML.java:254)
    	at com.isomorphic.xml.XML.toDSRecords(XML.java:262)
    	at com.isomorphic.xml.XML.toDSRecords(XML.java:265)
    	at com.isomorphic.datasource.BasicDataSource.<clinit>(BasicDataSource.java:1603)
    	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    	at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    	at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:126)
    	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:71)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:948)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:901)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:485)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:291)
    	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:288)
    	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:574)
    	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:895)
    	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:425)
    	at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:276)
    	at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:197)
    	at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:47)
    	at org.mortbay.jetty.handler.ContextHandler.startContext(ContextHandler.java:543)
    	at org.mortbay.jetty.servlet.Context.startContext(Context.java:136)
    	at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1220)
    	at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:513)
    	at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:448)
    	at com.google.gwt.dev.shell.jetty.JettyLauncher$WebAppContextWithReload.doStart(JettyLauncher.java:447)
    	at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
    	at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
    	at org.mortbay.jetty.handler.RequestLogHandler.doStart(RequestLogHandler.java:115)
    	at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
    	at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
    	at org.mortbay.jetty.Server.doStart(Server.java:222)
    	at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
    	at com.google.gwt.dev.shell.jetty.JettyLauncher.start(JettyLauncher.java:543)
    	at com.google.gwt.dev.DevMode.doStartUpServer(DevMode.java:421)
    	at com.google.gwt.dev.DevModeBase.startUp(DevModeBase.java:1035)
    	at com.google.gwt.dev.DevModeBase.run(DevModeBase.java:783)
    	at com.google.gwt.dev.DevMode.main(DevMode.java:275)
    
    +++ [INFO,ContextLoader,Thread-0] Root WebApplicationContext: initialization completed in 2363 ms
    === 2010-12-20 13:17:24,052 [ad-0] INFO  ISCInit - Auto-detected webRoot - using: /Users/marten/Work/EclipseWorkspaces/SocialDipping/war
    Any clue what is going on? Is it at all possible to use dependency injection within your custom datasources?

    PS: this is the code of the TwitterDataSource, for what it is worth
    Code:
    package nl.sytematic.projects.SocialDipping.server.customdatasources;
    
    /**
     * TwitterDatasource that can retrieve and send data to twitter.
     * Requires that the user is already authenticated using oAuth. Use TwitterAuthenticationServlet for this.
     * If the CRUD methods are called when there is no Auth key available, an error is returned.
     */
    public class TwitterDataSource extends BasicDataSource {
    
    	protected TwitterPublicKeyAndSecretContainer keys;
            //to be set using dependency injection
    	public void setKeysContainer(TwitterPublicKeyAndSecretContainer c) {
    		this.keys = c;
    	}
    
    	public TwitterPublicKeyAndSecretContainer getKeysContainer() {
    		return this.keys;
    	}
    
    	protected final int NOT_AUTHENTICATED_ON_TWITTER = -403; //lets use negative numbers for our errors
    
    	protected Twitter twitter = null;
    
    	/**
    	 * Post something on Twitter
    	 */
    	@Override
    	public DSResponse executeAdd(DSRequest req) throws Exception {
    		// Check if authorisation token and access token are stored in the logged in user profile.
    		// if not, return error, so that the client can redirect to the oAuth page using the TwitterAuthenticationServlet.
    		DSResponse res = new DSResponse();
    		if (!isAuthenticatedOnTwitter(req)) {
    			return getNotAuthenticatedDsResponse();
    		} else {
    			initializeAuthedTwitterConnection();
    		}
    
    		//authenticated, post on tweet parameter on twitter.
    		String tweet = (String) req.getFieldValue("tweet");
    
    		if (res.getErrors() == null || res.getErrors().isEmpty()) {
    			twitter.setStatus(tweet);
    		}
    
    		return res;
    
    	}
    ...
    
    }
    Last edited by Sytematic; 20 Dec 2010, 04:23.

    #2
    Yes, using dependency injection with custom DataSources and the documented approach is to set your serverConstructor to "spring:beanId".

    Are you using the "Init" servlet or did you use another approach to initialize the framework? This log appears to be impossible if you were using the Init servlet. The very first thing the Init servlet does is grab the servletContext (which the log subsequently complains is not available).

    If this log wasn't edited, it looks like a classloader issue in your IDE or servlet container.

    Comment


      #3
      Thanks, I didn't use the spring:beanId notation.

      However, I do get this error, but ONLY when the bean is declared. As soon as I remove the bean declaration, the error dissappears (but of course, then the bean doesn't work).

      Oh, and yes, I do use the Init servlet, this is how it is written in my web.xml
      Code:
      <!-- ISC init: initializes ISC framework -->  
       <servlet> 
         <servlet-name>Init</servlet-name>  
         <servlet-class>com.isomorphic.base.Init</servlet-class>  
         <load-on-startup>1</load-on-startup> 
       </servlet>
      , assuming that that is what you meant with 'using the Init servlet to initialize the system'.

      So I don't think it is an IDE issue to be honest: adding the bean causes the error, removing it, makes it go away. Side note: I do not have other custom datasources, that are initialized as beans, only this one.

      Might it be because of a bug in the nightly of the power edition of 13-12-2010?

      PS: this is my twitter.ds.xml file, but NOT putting 'twitter' on the datasource loader url in my hostpage.html does not make the error go away.
      Code:
      <?xml version="1.0" encoding="UTF-8"?>
      <DataSource 
          ID="twitter" 
          serverConstructor="spring:twitterDataSource"
          requiresAuthentication="true"> 
        <fields>
        	 <field name="twitterUsername" type="text" length="255" primaryKey="true" />
        	 <field name="twitterAccessToken" type="text" length="255" hidden="true"/>
        	 <field name="twitterAccessSecret" type="text" length="255" hidden="true"/>
        	 <field name="tweet" type="text" length="140" />
        </fields>  
      </DataSource>
      Last edited by Sytematic; 21 Dec 2010, 01:52.

      Comment


        #4
        Update: if I remove the 'extends BasicDataSource' part in my TwitterDataSource class, it does NOT throw the error (but of course, the datasource does not work then...) !

        So it seems that it (BasicDataSource?) somehow does not 'release' the context, yielding the error 'servletContext not yet available', as the log indicates. Or not. Fact is, I cannot really debug this. Could you guys look into this?

        An empty class like:

        Code:
        public class TwitterDataSource extends BasicDataSource {}
        Already triggers the error. Removing the 'extends BasicDataSource', makes the error go away....
        Last edited by Sytematic; 21 Dec 2010, 06:27.

        Comment


          #5
          This is yet more evidence that you have a classloader issue.

          There is no "releasing" or pooling of the servletContext. There's just one, captured when the Init servlet runs.

          Code:
              public static synchronized void go(ServletContext context)
              {
                  if(context != null) ISCFile.servletContext = context;
                  ISCInit.go();
              }
          .. and then here's the code that logs the subsequent error:

          Code:
          if(servletContext == null) {
                      log.error("Configured for containerIO, but servletContext not available!");
          }
          There's really only one explanation, and that is that multiple instances of the server framework classes have been loaded. In one context, the Init servlet ran and captured the context. In the other, the Init servlet never ran.

          This situation can arise from doing certain things like adding the same .jars to the classpath in multiple locations.

          You should also try compiled mode to see if it's just some Eclipse issue with how the project is being started.

          Comment


            #6
            Ah ok, that makes sense.

            Thanks for the info, I will look in to this!

            Comment


              #7
              I know this post is old but I ran into the same problem and it wasn't due to multiple instances of the server framework..

              I'm using Spring 3.0 (through Grails) and I found that Spring 3 does a Class.forName on my DataSource even if I specify lazyInit=true.

              I applied the same work around as described here: http://forum.springsource.org/showth...ing-3.0-vs-2.5 - I created a Factory class that returns a new instance of my DataSource class. This fixed the Init problem for me.
              Last edited by jaredm; 23 Jun 2011, 13:49.

              Comment


                #8
                Ah, so looking back at Sytematic's logs, you're saying those logs are happening because Spring is triggering the SmartGWT server's static initializer, and *not* because of the Init servlet?

                If so, how is Spring managing to run before the Init servlet?

                Comment


                  #9
                  I'm not sure that Systematic's problem is the same as mine, but it is certainly an alternative explanation.

                  In my case I think Spring is managing to run before the Init servlet because Grails loads its own servlet which I can't influence:
                  Code:
                  	<servlet>
                  		<servlet-name>grails</servlet-name>
                  		<servlet-class>org.codehaus.groovy.grails.web.servlet.GrailsDispatcherServlet</servlet-class>
                  		<load-on-startup>1</load-on-startup>
                  	</servlet>

                  Comment


                    #10
                    Got it - yes, we agree that the explanation is basically that Grails/Spring is creating an instance of your class before any other code in the system runs and before out InitServlet, which we require to run first.

                    There's not a lot we can do about our server classes not working in this mode - they need that servletContext object or they can't use the container to read critical configuration files. So the solutions are:

                    1. find some way to convince grails to use load-on-startup 2 for it's servlet

                    2. use the factory pattern you mentioned so that the object that Spring creates at startup doesn't trigger static initializers

                    3. Spring fixes their apparent bug with lazy init

                    4. We provide a somewhat weird API allowing someone to initialize our server framework by passing in a servletContext, and you use this with any beans created with Spring by having Spring give you the servletContext via dependency injection (if it can even do this)

                    Comment


                      #11
                      I had this same problem, where Spring was loading before SmartClient. This caused problems when initializing custom DataSources in spring. I'm using Spring 3 and SmartClient 8.2.

                      The workaround I came up with was to initialize SmartClient not using the Init servlet, but via a Listener instead. According to the spec, listeners are loaded in the order in which they appear in web.xml, so if my SmartClientInitListener comes before my Spring Listener, then these two items should load in the correct order. I'm using this in my code and things appear to be working so far.

                      Here is the Listener:

                      Code:
                      import javax.servlet.ServletContext;
                      import javax.servlet.ServletContextEvent;
                      import javax.servlet.ServletContextListener;
                      
                      import com.isomorphic.base.ISCInit;
                      import com.isomorphic.io.ISCFile;
                      
                      public class SmartClientInitListener implements ServletContextListener {
                      
                      	@Override
                      	public void contextInitialized(ServletContextEvent sce) {
                      
                      		ServletContext servletContext = sce.getServletContext();
                      		if (servletContext != null) {
                      			ISCFile.servletContext = servletContext;
                      		}
                      		ISCInit.go();
                      	}
                      
                      	@Override
                      	public void contextDestroyed(ServletContextEvent sce) {
                      
                      	}
                      
                      }
                      A snippet from my web.xml:

                      Code:
                      	<!--  Initialize SmartClient.  This has to come before Spring listeners. -->
                      	<listener>
                      		<listener-class>com.etouchpoint.common.web.smartclient.SmartClientInitListener</listener-class>
                      	</listener>
                      
                      	<!-- Spring -->
                      	<context-param>
                      		<param-name>contextConfigLocation</param-name>
                      		<param-value>/WEB-INF/spring.xml</param-value>
                      	</context-param>
                      	<listener>
                      		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
                      	</listener>
                      	<listener>
                      		<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
                      	</listener>
                      Like I said, this appears to work; however, there could be some functionality that I'm missing that I'm unaware of. I don't have the Init servlet source code, so I don't really know all the things that go on there. Perhaps Isomorphic can comment on whether this does everything necessary to actually initialize all parts of the system.

                      Comment


                        #12
                        Thanks mstickel, that seems like it must be the right way to ensure Spring isn't calling us before we've initialized. We'll try out this approach and make it the official way that we do initialization if it works.

                        Comment

                        Working...
                        X