0

I'm in the process of upgrading the spring framework version used in our webapp from 3.1.4 to 4.1.8. With the new Spring version, A few of our unit tests are failing because @Autowired is no longer working. This is one of the failing tests:

@ContextConfiguration(locations={"/math-application-context.xml"})
public class MathematicaMathServiceTest extends JavaMathServiceTest{

@Autowired
private KernelLinkPool mathematicalKernelPool; 

protected static String originalServiceType = System.getProperty("calculation.math.service.type");

@AfterClass
public static void unsetMathServiceType(){
    System.clearProperty("calculation.math.service.type");

}

@BeforeClass
public static void setMathServiceType(){
    System.setProperty("calculation.math.service.type","Mathematica");
}

@Test
public void testMathematicaService() throws Exception{          


    try {           

        acquireKernelAndExecute(0);

        Assert.assertEquals(0, mathematicalKernelPool.getBorrowingThreadsCount());

    } catch(UnsatisfiedLinkError e) {
        System.out.println("Mathematica not installed. Skipping test");
    }catch(Exception ex){
        if (!ExceptionFormatter.hasCause(ex, MathServiceNotConfiguredException.class)){throw ex;}
        if (System.getProperty(MathService.SERVICE_CONFIGURED_SYSTEM_VARIABLE) != null){
            throw ex;
        }
        logger.error("Cannot execute test. Math service is not configured");
    }
}

}

This is the KernelLinkPool class:

public class KernelLinkPool extends GenericObjectPool implements InitializingBean{

private static final int RETRY_TIMEOUT_MS = 5000;

private static final long STARTUP_WAIT_TIME_MS = 10000;

private boolean mathematicaConfigured = true;
private PoolableObjectFactory factory;
// ensures that multiple requests from the same thread will be given the same KernelLink object
private static ThreadLocal<KernelLink> threadBoundKernel = new ThreadLocal<KernelLink>();
// holds the number of requests issued on each thread
private static ThreadLocal<Integer> callDepth = new ThreadLocal<Integer>();
private long maxBorrowWait;
private Integer maxKernels;
private boolean releaseLicenseOnReturn;
private Logger logger = LoggerFactory.getLogger(this.getClass());
// (used only for unit testing at this point)
private Map<String,Integer> borrowingThreads = new ConcurrentHashMap<String,Integer>();

public KernelLinkPool(PoolableObjectFactory factory) {
    super(factory);     
    this.factory = factory;
    this.setMaxWait(maxBorrowWait);

}

@Override
public Object borrowObject() throws Exception{
    return borrowObject(this.maxBorrowWait);
}

public Object borrowObject(long waitTime) throws Exception {
    long starttime = System.currentTimeMillis();

    if (!mathematicaConfigured){
        throw new MathServiceNotConfiguredException();
    }

    try{
        if (callDepth.get() == null){
            callDepth.set(1);
        }else{
            callDepth.set(callDepth.get()+1);
        }

        KernelLink link = null;         
        if (threadBoundKernel.get() != null){
            link = threadBoundKernel.get();
        }else{
            //obtain kernelLink from object pool
            //retry when borrowObject fail until
            //maxBorrowWait is reached
            while(true){
                try{
                    logger.debug("Borrowing MathKernel from object pool");
                    link = (KernelLink) super.borrowObject();
                    break;
                }catch(KernelLinkCreationException ex){
                    long timeElapsed = System.currentTimeMillis() - starttime;
                    logger.info("Failed to borrow MathKernel. Time elapsed [" + timeElapsed + "] ms", ex);
                    if(timeElapsed >= waitTime){
                        logger.info("Retry timeout reached");
                        throw ex;
                    }
                    Thread.sleep(RETRY_TIMEOUT_MS);
                }
            }
            logger.debug("borrowed [" + link + "]");
            threadBoundKernel.set(link);
        }

        borrowingThreads.put(Thread.currentThread().getName(),callDepth.get());

        return link;

    }catch(Exception ex){
        logger.error("Failed to acquire Mathematica kernel. Borrowing threads [" + borrowingThreads + "]");
        throw ex;
    }
}


public void returnObject(Object obj) throws Exception {

    callDepth.set(callDepth.get()-1);

    if (callDepth.get() <= 0){
        threadBoundKernel.set(null);

        borrowingThreads.remove(Thread.currentThread().getName());

        if (releaseLicenseOnReturn){
            // will destroy obj
            super.invalidateObject(obj);
        }
        else{
            // will park obj in the pool of idle objects
            super.returnObject(obj);
        }
    }else{
        borrowingThreads.put(Thread.currentThread().getName(),callDepth.get());
    }

}


@Override
public void afterPropertiesSet() throws Exception {

    try{

        if (maxKernels == 0){
            List<KernelLink> links = new ArrayList<KernelLink>();
            while (true){
                try{
                    links.add((KernelLink)factory.makeObject());
                }catch(KernelLinkCreationException ex){
                    break;
                }
            }       
            if(links.isEmpty()){
                logger.warn("No available Mathematica license!");
                mathematicaConfigured = false;
                return;
            }
            for (KernelLink link : links){
                factory.destroyObject(link);
            }
            logger.info("Detected number of available Mathematica license = [" + links.size() + "]");
            setMaxActive(links.size());
            setMaxIdle(links.size());
        }else{
            if(maxKernels < 0){
                logger.info("Set number of Mathematica license to no limit");
            }else{
                logger.info("Set number of Mathematica license to [" + maxKernels + "]");
            }
            setMaxActive(maxKernels);
            setMaxIdle(maxKernels);         
        }

        Object ob = borrowObject(STARTUP_WAIT_TIME_MS);
        returnObject(ob);                   

        mathematicaConfigured = true;
    }catch(Throwable ex){
        logger.warn("Mathematica kernel pool could not be configured: ", ex.getMessage());
        mathematicaConfigured = false;
    }
}


public int getBorrowingThreadsCount() {
    return borrowingThreads.size();
}

public Integer getMaxKernels() {
    return maxKernels;
}

public void setMaxKernels(Integer maxKernels) {
    this.maxKernels = maxKernels;
}

public boolean isMathematicaConfigured(){
    return mathematicaConfigured;
}

public boolean isReleaseLicenseOnReturn() {
    return releaseLicenseOnReturn;
}

public void setReleaseLicenseOnReturn(boolean releaseLicenseOnReturn) {
    this.releaseLicenseOnReturn = releaseLicenseOnReturn;
}

public long getMaxBorrowWait() {
    return maxBorrowWait;
}

public void setMaxBorrowWait(long maxBorrowWait) {
    this.maxBorrowWait = maxBorrowWait;
}       
}

The tests are failing with this exception:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.etse.math.wolfram.KernelLinkPool] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

This is the math-application-context file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

<beans profile="unitTest,integratedTest,activeServer">
    <bean class="org.springframework.jmx.export.MBeanExporter"
        lazy-init="false">
        <property name="registrationBehaviorName" value="REGISTRATION_IGNORE_EXISTING" />
        <property name="beans">
            <map>
                <entry key="etse.math:name=MathematicalKernelFactory"
                    value-ref="mathematicalKernelFactory" />
                <entry key="etse.math:name=MathematicalKernelPool" value-ref="mathematicalKernelPool" />
            </map>
        </property>
    </bean>

    <bean id="mathService" class="com.etse.math.MathServiceFactoryBean">
        <property name="mathServiceType" value="${calculation.math.service.type}"/>
        <property name="mathematicaService" ref="mathematicaService"/>
    </bean> 

    <bean id="mathematicaService" class="com.etse.math.wolfram.MathematicaService">
        <property name="kernelPool" ref="mathematicalKernelPool" />
        <property name="minParallelizationSize" value="${calculation.mathematica.kernel.parallel.batch.size}" />
    </bean>

    <bean id="mathematicalKernelPool" class="com.etse.math.wolfram.KernelLinkPool"
        destroy-method="close">
        <constructor-arg ref="mathematicalKernelFactory" />
        <property name="maxKernels" value="${calculation.mathematica.max.kernels}" />
        <property name="maxBorrowWait"
            value="${calculation.mathematica.kernel.borrow.max.wait}" />
        <property name="releaseLicenseOnReturn"
            value="${calculation.mathematica.kernel.release.license.on.return}" />
    </bean>

    <bean id="mathematicalKernelFactory" class="com.etse.math.wolfram.KernelLinkFactory">
        <property name="debugPackets" value="false" />
        <property name="linkMode" value="launch" />
        <property name="mathematicaKernelLocation" value="${calculation.mathematica.kernel.location}" />
        <property name="mathematicaLibraryLocation" value="${calculation.mathematica.library.location}" />
        <property name="mathematicaAddOnsDirectory" value="${calculation.mathematica.addons.directory}" />
        <property name="linkProtocol" value="sharedMemory" />
    </bean>
</beans>

<beans profile="passiveServer,thickClient,tools">
        <bean id="mathService" class="com.etse.math.DummyMathService"/>
</beans>

I also tried using the application context to load the bean, but that failed with the following exception:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'mathematicalKernelPool' is defined

If I remove the autowired field, the test fails with a NoSuchBeanDefinitionException for another bean (mathService) that is loaded via the application context in a super class. So it appears that the application context from math-application-context is not loaded for some reason. Any idea of what could be happening here? Thank you.

UPDATE:

I took a look at the beans defined in the application context and confirmed that none of the beans defined in math-application-context are present. The application context contains only beans defined in another context file loaded by the super class. Why would it fail to load math-application-context?

MatH
  • 9
  • 3
  • Could you post the class KernelLinkPool ? You may need to add also dependency of KernelLinkPool class. – Ogma Aug 16 '16 at 20:11
  • @Autowired is working perfectly fine, as it's trying to inject the beans. You're getting the error that these beans are not found on the classpath. Check if the `base-package` includes the required bean classes. – 11thdimension Aug 16 '16 at 20:15
  • Added KernelLinkPool.class – MatH Aug 16 '16 at 20:17
  • I think its duplicate of this post http://stackoverflow.com/questions/20333147/autowired-no-qualifying-bean-of-type-found-for-dependency – Amit Mahajan Aug 16 '16 at 22:10
  • Yes I saw that post. This is related to spring migration so I thought it warranted its own thread. It works with spring 3 – MatH Aug 16 '16 at 22:36

2 Answers2

0

At this point I would honestly get rid of the XML config and go total annotation/code based. Create a Config class and have it create any beans you need to be autowired.

Gandalf
  • 9,648
  • 8
  • 53
  • 88
0

It was a profile issue. The super class to the test was using:

@ProfileValueSourceConfiguration(TestProfileValueSource.class)

to set the profile, but it was not working. After removing that annotation I added:

@ActiveProfiles(resolver=TestProfileValueSource.class) and now its working again.

MatH
  • 9
  • 3