Announcement Announcement Module
Collapse
No announcement yet.
ApplicationContext in Ant Task. Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • ApplicationContext in Ant Task.

    I am trying to create an ApplicationContext (specifically a FileSystemXmlApplicationContext) in a custom Ant task I have written. It finds the XML file correctly, and then throws NoClassDefFoundException for a bean that is the DriverManagerDataSource....
    Right before I init the context I can call Class.forName() for the driver manager class and it loads fine, so why won't the bean factory find it?
    what classpath concept am I missing? I have pored through my Spring in Action book and these forums and still haven't a solution.
    Any help would be greatly appreciated.

  • #2
    Re: ApplicationContext in Ant Task.

    Originally posted by stibrian
    I am trying to create an ApplicationContext (specifically a FileSystemXmlApplicationContext) in a custom Ant task I have written. It finds the XML file correctly, and then throws NoClassDefFoundException for a bean that is the DriverManagerDataSource....
    Right before I init the context I can call Class.forName() for the driver manager class and it loads fine, so why won't the bean factory find it?
    what classpath concept am I missing? I have pored through my Spring in Action book and these forums and still haven't a solution.
    Any help would be greatly appreciated.
    Did my article on integrating Spring into Ant help in any way?
    See: http://www.javaworld.com/javaworld/j...antspring.html

    Comment


    • #3
      Useful article

      The article is nice, and I'm going to look at using your technique in the future (maybe this time if I keep having issues). I am actually working towards something much simpler though.
      My Task:
      Code:
      private void setupBeanFactory() throws BeansException, FileNotFoundException, ClassNotFoundException{
              
              Class.forName("org.springframework.jdbc.datasource.DriverManagerDataSource");
              
              FileSystemXmlApplicationContext cp2 = new     FileSystemXmlApplicationContext("etc/config/config.xml");
            }
          
          /** 
           * The main point of entry when ant calls our custom task.
           */
          public void execute() throws BuildException {
              try {
                  setupBeanFactory();
              } catch (Exception e) {
                              e.printStackTrace();
              }
          }
      My config.xml:
      Code:
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
      "http&#58;//www.springframework.org/dtd/spring-beans.dtd">
      <beans>
          <!--  our jndi datasource -->
      	<bean id="dataSource"
      		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      		<property name="url">
      			<value>jdbc&#58;mysql&#58;//localhost/dbName?autoReconnect=true</value>
      		</property>
      		<property name="username">
      			<value>password</value>
      		</property>
      		<property name="password">
      			<value>password</value>
      		</property>
      		<property name="driverClassName">
      			<value>org.gjt.mm.mysql.Driver</value>
      		</property>
      	</bean>
      
      	<!--  create the session factory with the correct dialect with our datasource -->
      	<bean id="sessionFactory"
      		class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
      		<property name="dataSource">
      			<ref bean="dataSource" />
      		</property>
      		<property name="hibernateProperties">
      			<props>
      				<prop key="hibernate.dialect">
      					net.sf.hibernate.dialect.MySQLDialect
      				</prop>
      			  <!-- <prop key="hibernate.show_sql">
      			  	true
      			  </prop>-->
      			</props>
      		</property>
      		<property name="mappingDirectoryLocations">
      			<list>
      				<value>classpath&#58;my class mappings directory</value>
      			</list>
      		</property>
      	</bean>
      
      	<!--  create the hibernate template based on our session factory -->
      	<bean id="hibernateTemplate"
      		class="org.springframework.orm.hibernate.HibernateTemplate">
      		<property name="sessionFactory">
      			<ref bean="sessionFactory" />
      		</property>
      	</bean>
      	
      	
      	
      </beans>
      Now when Ant executes I get this stack trace:
      Code:
      &#91;exportDomainData&#93; &#40;xml.XmlBeanDefinitionReader         119 &#41; Loading XML bean definitions from file &#91;C&#58;\java\eclipse\workspace\hit\etc\config\config.xml&#93;
      &#91;exportDomainData&#93; org.springframework.beans.factory.BeanDefinitionStoreException&#58; Error registering bean with name 'dataSource' defined in file &#91;C&#58;\java\eclipse\workspace\hit\etc\config\config.xml&#93;&#58; Bean class &#91;org.springframework.jdbc.datasource.DriverManagerDataSource&#93; not found; nested exception is java.lang.ClassNotFoundException&#58; org.springframework.jdbc.datasource.DriverManagerDataSource
      &#91;exportDomainData&#93; java.lang.ClassNotFoundException&#58; org.springframework.jdbc.datasource.DriverManagerDataSource
      &#91;exportDomainData&#93; at java.net.URLClassLoader$1.run&#40;URLClassLoader.java&#58;199&#41;
      &#91;exportDomainData&#93; at java.security.AccessController.doPrivileged&#40;Native Method&#41;
      &#91;exportDomainData&#93; at java.net.URLClassLoader.findClass&#40;URLClassLoader.java&#58;187&#41;
      &#91;exportDomainData&#93; at java.lang.ClassLoader.loadClass&#40;ClassLoader.java&#58;289&#41;
      &#91;exportDomainData&#93; at sun.misc.Launcher$AppClassLoader.loadClass&#40;Launcher.java&#58;274&#41;
      &#91;exportDomainData&#93; at java.lang.ClassLoader.loadClass&#40;ClassLoader.java&#58;235&#41;
      &#91;exportDomainData&#93; at java.lang.ClassLoader.loadClassInternal&#40;ClassLoader.java&#58;302&#41;
      &#91;exportDomainData&#93; at java.lang.Class.forName0&#40;Native Method&#41;
      &#91;exportDomainData&#93; at java.lang.Class.forName&#40;Class.java&#58;219&#41;
      &#91;exportDomainData&#93; at org.springframework.beans.factory.support.BeanDefinitionReaderUtils.createBeanDefinition&#40;BeanDefinitionReaderUtils.java&#58;60&#41;
      &#91;exportDomainData&#93; at org.springframework.beans.factory.xml.DefaultXmlBeanDefinitionParser.parseBeanDefinition&#40;DefaultXmlBeanDefinitionParser.java&#58;306&#41;
      &#91;exportDomainData&#93; at org.springframework.beans.factory.xml.DefaultXmlBeanDefinitionParser.parseBeanDefinition&#40;DefaultXmlBeanDefinitionParser.java&#58;274&#41;
      &#91;exportDomainData&#93; at org.springframework.beans.factory.xml.DefaultXmlBeanDefinitionParser.registerBeanDefinitions&#40;DefaultXmlBeanDefinitionParser.java&#58;186&#41;
      &#91;exportDomainData&#93; at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions&#40;XmlBeanDefinitionReader.java&#58;175&#41;
      &#91;exportDomainData&#93; at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions&#40;XmlBeanDefinitionReader.java&#58;133&#41;
      &#91;exportDomainData&#93; at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions&#40;AbstractBeanDefinitionReader.java&#58;99&#41;
      &#91;exportDomainData&#93; at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions&#40;AbstractXmlApplicationContext.java&#58;102&#41;
      &#91;exportDomainData&#93; at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions&#40;AbstractXmlApplicationContext.java&#58;70&#41;
      &#91;exportDomainData&#93; at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory&#40;AbstractRefreshableApplicationContext.java&#58;87&#41;
      &#91;exportDomainData&#93; at org.springframework.context.support.AbstractApplicationContext.refresh&#40;AbstractApplicationContext.java&#58;262&#41;
      &#91;exportDomainData&#93; at org.springframework.context.support.FileSystemXmlApplicationContext.<init>&#40;FileSystemXmlApplicationContext.java&#58;82&#41;
      &#91;exportDomainData&#93; at org.springframework.context.support.FileSystemXmlApplicationContext.<init>&#40;FileSystemXmlApplicationContext.java&#58;67&#41;
      &#91;exportDomainData&#93; at org.springframework.context.support.FileSystemXmlApplicationContext.<init>&#40;FileSystemXmlApplicationContext.java&#58;58&#41;
      &#91;exportDomainData&#93; at com.sourceallies.hit.util.ant.DomainDataExportTask.setupBeanFactory&#40;Unknown Source&#41;
      &#91;exportDomainData&#93; at com.sourceallies.hit.util.ant.DomainDataExportTask.execute&#40;Unknown Source&#41;
      &#91;exportDomainData&#93; at org.apache.tools.ant.UnknownElement.execute&#40;UnknownElement.java&#58;275&#41;
      &#91;exportDomainData&#93; at org.apache.tools.ant.Task.perform&#40;Task.java&#58;364&#41;
      &#91;exportDomainData&#93; at org.apache.tools.ant.Target.execute&#40;Target.java&#58;341&#41;
      &#91;exportDomainData&#93; at org.apache.tools.ant.Target.performTasks&#40;Target.java&#58;369&#41;
      &#91;exportDomainData&#93; at org.apache.tools.ant.Project.executeTarget&#40;Project.java&#58;1214&#41;
      &#91;exportDomainData&#93; at org.apache.tools.ant.Project.executeTargets&#40;Project.java&#58;1062&#41;
      &#91;exportDomainData&#93; at org.eclipse.ant.internal.ui.antsupport.InternalAntRunner.run&#40;InternalAntRunner.java&#58;387&#41;
      &#91;exportDomainData&#93; at org.eclipse.ant.internal.ui.antsupport.InternalAntRunner.main&#40;InternalAntRunner.java&#58;136&#41;
      Note the call to Class.forName() that loads the same class right before I try to load the ApplicationContext. It succeeds, and you can see from the stack trace that my Spring configuration file is being propertly loaded and read, but barfs when trying to instantiate the DriverManager. Why?

      I'm a relative Spring newbie, and I'm pretty sure there's something fundamental I'm missing here, but what is it?

      BTW - nice article. I've bookmarked it for future use as I wish to do more Ant/Spring stuff, but this is a quick and dirty and I can't make that work.

      Why won't the FileSystemXmlApplicationContext load a class from the same classpath that the code on the preceding line is using?

      Thanks again for the help.

      Brian

      Comment


      • #4
        I haven't looked closely at your stack traces (I'm at work right now), however, when working on the Ant project, I did run into class loader issues. The way Ant does classloader stuff does not allow straightforward reuse. I used a technique I found in the Ant mailing list. Not sure if this is applicable to your exact problem.

        Comment


        • #5
          ant classloader

          I'll look at your code more closely to see what you're talking about, but if it is an ant classloader issue then why can I load the class with Class.forName() but not with the ApplicationContext? I'm not a classloading genius so maybe I'm missing a subtlety here.... i'm rather confused.

          Comment


          • #6
            Class.forName() uses the classloader that loaded the current class, while I think ApplicationContext's implementations use the current thread context classloader. That might be what caused the different behavior (not sure how exactly though).

            Comment


            • #7
              I can't say as I understand the difference between the classloader that loaded the current class and the thread context classloader. I have a single thread of execution, why aren't they the same? As I said, I'm not a classloader guru, but am now more confused than ever.
              If these 2 classloaders are in fact different, this begs the question: how do I tell Spring to use the classload that loaded the class I'm CALLING SPRING FROM, rather than some other one. I don't see how that behavior would make sense, but I'm always looking to be schooled, or at least pointed in the right direction.

              Comment


              • #8
                Classloader limitations are the floppy disk of the 21st century.

                In my Ant implementation's AbstractAntTask I have:

                Code:
                        // code to handle cnf issues with taskdef classloader
                        AntClassLoader2 antClassLoader = null;
                        Object obj = this.getClass&#40;&#41;.getClassLoader&#40;&#41;;
                        if &#40;obj instanceof AntClassLoader2&#41; &#123;
                            antClassLoader = &#40;AntClassLoader2&#41; obj;
                            antClassLoader.setThreadContextLoader&#40;&#41;;
                        &#125;
                        // end code to handle classnotfound issue
                But, my issue was that the unit tests would not run in both command line and Eclipse. Anyway, I'm not a classloader expert.

                Comment


                • #9
                  Originally posted by stibrian
                  I can't say as I understand the difference between the classloader that loaded the current class and the thread context classloader. I have a single thread of execution, why aren't they the same? As I said, I'm not a classloader guru, but am now more confused than ever.
                  I don't understand how exactly it didn't work either. Why don't you print out the class names of the two classloaders - even just for the sake of knowing?

                  Originally posted by stibrian
                  If these 2 classloaders are in fact different, this begs the question: how do I tell Spring to use the classload that loaded the class I'm CALLING SPRING FROM, rather than some other one. I don't see how that behavior would make sense, but I'm always looking to be schooled, or at least pointed in the right direction.
                  Have you tried using setContextClassLoader as jbetancourt pointed out?

                  Comment


                  • #10
                    Having gotten a fix (thanks a million jbetancourt ), I plan to do more more investigation/education about classloaders in general. the code from teh abstract ant task that jbetancourt posted did the trick, but I hate to use even asnippet of code if I don't understand why its there - that's just prgramming by coincidence. :evil:

                    Funny thing, this same snippet of code was used to modify the Jasperreports ant compiler task.... now I REALLY have to understand what's going on.

                    Thanks for all the help, and I'll post any other information relevant to this topic here.

                    Brian

                    Comment


                    • #11
                      To get this to work with ant 1.7, I've had to modify jbetancourt's example to use plain old AntClassLoader :

                      Code:
                             AntClassLoader antClassLoader = null;
                              Object obj = this.getClass().getClassLoader();
                              if (AntClassLoader.class.isAssignableFrom(obj.getClass())) {
                                  antClassLoader = (AntClassLoader) obj;
                                  antClassLoader.setThreadContextLoader();
                              }
                      Otherwise I was getting a ClassNotFoundException .

                      Comment


                      • #12
                        improved way of dealing w/ this

                        This is an old thread - but I'm posting this just so the next guy like myself has an easier time of it.

                        It looks like what happens is that when you do a taskdef in your build.xml, like this:

                        Code:
                        <taskdef name="mytask" classname="com.blah.BlorpTask" classpathref="yourClassPathDefinedAbove"/>
                        it creates a new class loader specifically for that task - and loads that task and it's dependent classes with it's own class loader - from the class path you specify. BUT it does NOT set the class path for the current thread to this same class loader.

                        So inside your ant task
                        Code:
                        Thread.currentThread().getContextClassLoader().loadClass("com.blah.Blorp")
                        (which is essentially what Spring does - from what I can tell) will NOT use the class loader of the Ant task itself. I don't know what class loader is set for the current thread by default - but it's not the one you expect.

                        Rather than setting the current thread's class loader with:

                        Code:
                        ...
                        public void execute() throws BuildException {
                            Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader();
                        }
                        ...
                        A smoother approach is to tell Spring to explicitly use the same class loader that loaded your task - this should have less potential for side effects (who knows what calling
                        Code:
                        Thread.currentThread().setContextClassLoader()
                        might cause - maybe none, but it's definitely not the safest).

                        The code looks like this:

                        Code:
                        ...
                        public void execute() throws BuildException {
                            AbstractApplicationContext myApplicationContext =
                                new FileSystemXmlApplicationContext(
                                    new String[] { pathToSpringConfigFile },
                                    false);
                            myApplicationContext.setClassLoader(this.getClass().getClassLoader());
                            myApplicationContext.refresh();
                            ...
                        }
                        ...
                        This fixed it for me and seems to be a good approach.

                        One other note - when I did this I ran into another issue where the paths were not resolving correctly. In my IDE (Netbeans 6.1), the current working directory was set to "C:\Program Files\Netbeans 6.1\" and so none of the paths resolved correctly. This was solved easily by overriding the getResourceByPath() method on FileSystemXmlApplicationContext and telling it to explicitly resolve paths against the project base directory. Like this:


                        Code:
                        ...
                        public void execute() throws BuildException {
                            AbstractApplicationContext myApplicationContext =
                                new FileSystemXmlApplicationContext(
                                    new String[] { pathToSpringConfigFile },
                                    false) {
                                        protected Resource getResourceByPath(String aPath) {
                                            Resource myResource = new FileSystemResource(
                                                    new File(getProject().getBaseDir(), aPath));
                                            System.out.println("Resolved " + aPath + " -> " + myResource);
                                            return myResource;
                                        }
                            }
                            myApplicationContext.setClassLoader(this.getClass().getClassLoader());
                            myApplicationContext.refresh();
                            ...
                        }
                        ...
                        Last edited by bradpeabody; Oct 23rd, 2008, 01:32 PM.

                        Comment

                        Working...
                        X