Q:

Unable to get Quartz task to run in Spring web mvc

I am a developer returning back to Java after about 5 years of .NET experience. I am working on a web app project where I need to update a DB and make an http post every 15 mins. I have put together the following but although it shows me in tomcat, that a thread has been started to execute the Quartz job, it does not do anything. The Database is not updated. For the records, the JPA hibernate implementation that I have works. I have tested it because there is a CRUD form too.
For the scheduled task, I have two classes: I have a ContextListener class that will extend the ServletContextListener. It will be registered with the servlet container or AS and scheduled to start when it starts. In this case the AS is Tomcat 7.0.57. I also have a ApiKeyExpirationJob class that represents the job proper. I have also added some config to the web.xml file to instruc Tomcat to start the job whenever it starts.
ContextListener class, that initializes the job in the bounds of the context.
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.quartz.DateBuilder;
import org.quartz.DateBuilder.IntervalUnit;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;

import org.apache.log4j.Logger;

public class ContextListener implements ServletContextListener
{

    /*private static final Logger           LOGGER           =
Logger.getLogger(ContextListener.class);*/

    // Initiate a Schedule Factory
    private static final SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    // Retrieve a scheduler from schedule factory (Lazy init)
    private Scheduler                     scheduler        = null;

    @Override
    public void contextDestroyed(ServletContextEvent arg0)
    {
        try
        {
            if (scheduler != null && scheduler.isStarted())
                scheduler.shutdown();
        }
        catch (SchedulerException e)
        {
                //LOGGER.error(e);
        }
    }

    @Override
    public void contextInitialized(ServletContextEvent arg0)
    {

        //LOGGER.info("----- Initializing quartz -----");
        try
        {
            scheduler = schedulerFactory.getScheduler();

            // Initiate JobDetail with job name, job group, and executable job class
            JobDetail jobDetail = JobBuilder.newJob(ApiKeyExpirationJob.class)
.withIdentity("db_refresher", "refresher")
.build();

           // Initiate SimpleTrigger with its name and group name.
           SimpleTrigger simpleTrigger = TriggerBuilder.newTrigger()
               .withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
               .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInHours(1).repeatForever())
               .startAt(DateBuilder.futureDate(15, IntervalUnit.MINUTE))
               .build();

           scheduler.scheduleJob(jobDetail, simpleTrigger);

           // start the scheduler
           //scheduler.start();

         }
         catch (SchedulerException se)
         {
              //LOGGER.error(se);
         }
         catch (Exception e)
         {
              //LOGGER.error(e);
         }
     }
}

ApiKeyExpirationJob class
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
//import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;

import com.google.gson.Gson;
import com.x.apimanager.exception.ApiNotFound;
import com.x.apimanager.model.Api;
import com.x.apimanager.service.ApiService;

@SuppressWarnings("deprecation")
public class ApiKeyExpirationJob implements Job
{
    /* private static final Logger           LOGGER           =
             Logger.getLogger(ApiKeyExpirationJob.class);*/

     @Autowired
        private ApiService apiService;
    // This is the method that will be called by the scheduler when the trigger fires the job.
    @Override
    public void execute(JobExecutionContext arg0) throws JobExecutionException
    {
        // Do you scheduled task here. This is usually the repetitive piece that runs for every firing of the trigger.
        //LOGGER.info("Executing scheduled job");
        List<Api> apiList = apiService.findAll();

        for(Api api : apiList) {
            //System.out.println(api.getApiKey());
            //final SimpleDateFormat df = new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss" );
            final java.util.Calendar cal = GregorianCalendar.getInstance();
            cal.setTime( api.getCreatedDate() );
            cal.add( GregorianCalendar.MINUTE, 1 ); // date manipulation
            Date dateExpectedExpiryDate = cal.getTime();
            Date dateNow = new Date();
            if ((dateNow.compareTo(dateExpectedExpiryDate) > 1) || (dateNow.compareTo(dateExpectedExpiryDate) == 0))
            {
                api.setIsExpired(true);

                try {
                    apiService.update(api);
                    System.out.println("Updating API");

                    //post the entity as JSON
                    String postUrl=api.getUrl();// put in your url
                    Gson gson= new Gson();
                    HttpPost post = new HttpPost(postUrl);
                    StringEntity  postingString = new StringEntity(gson.toJson(api));//convert your pojo to   json
                    System.out.println(postingString.toString());
                    post.setEntity(postingString);
                    post.setHeader("Content-type", "application/json");
                    @SuppressWarnings("deprecation")
                    HttpClient httpClient = new DefaultHttpClient();
                    HttpResponse  response = httpClient.execute(post);
                } catch (ApiNotFound | IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        } 
    }
}

The web application uses the Java based configuration approach of annotations and a WebAppConfig class. This application config java files is shown below. I believe this may be were I am having challenges. Most examples I find online use the xml configuration approach but apart from converting some of the classes, I don't know how to map some other configuration attributes to this class.
import java.util.Properties;

import javax.annotation.Resource;
import javax.sql.DataSource;

import org.hibernate.ejb.HibernatePersistence;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;

@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan("com.x.apimanager")
@PropertySource("classpath:application.properties")
@EnableJpaRepositories("com.x.apimanager.repository")
public class WebAppConfig {

    private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
    private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
    private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
    private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";

    private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
    private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
    private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";

    @Resource
    private Environment env;

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();

        dataSource.setDriverClassName(env.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
        dataSource.setUrl(env.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
        dataSource.setUsername(env.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
        dataSource.setPassword(env.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));

        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
        entityManagerFactoryBean.setPackagesToScan(env.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));

        entityManagerFactoryBean.setJpaProperties(hibProperties());

        return entityManagerFactoryBean;
    }

    private Properties hibProperties() {
        Properties properties = new Properties();
        properties.put(PROPERTY_NAME_HIBERNATE_DIALECT, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
        properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
        return properties;
    }

    @Bean
    public JpaTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    }

    @Bean
    public UrlBasedViewResolver setupViewResolver() {
        UrlBasedViewResolver resolver = new UrlBasedViewResolver();
        resolver.setPrefix("/WEB-INF/pages/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }

    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasename(env.getRequiredProperty("message.source.basename"));
        source.setUseCodeAsDefaultMessage(true);
        return source;
    }

    @Bean
    public org.apache.activemq.ActiveMQConnectionFactory connectionFactory() {
        org.apache.activemq.ActiveMQConnectionFactory connectionFactory = new org.apache.activemq.ActiveMQConnectionFactory();
        connectionFactory.setBrokerURL("tcp://localhost:61616");
        return connectionFactory;
    }

    @Bean
    public org.springframework.jms.core.JmsTemplate jmsTemplate() {
        org.springframework.jms.core.JmsTemplate jmsTemplate = new org.springframework.jms.core.JmsTemplate(connectionFactory());
        jmsTemplate.setDefaultDestinationName("apiqueue");
        return jmsTemplate;
    }

    /*@Bean
    public com.x.apimanager.scheduler.ContextListener contextListener(){
        com.x.apimanager.scheduler.ContextListener contextListener = new com.x.apimanager.scheduler.ContextListener();
        contextListener.
    }*/
    /* @SuppressWarnings("deprecation")
    @Bean
        public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter()
        {
            final AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter = new AnnotationMethodHandlerAdapter();
            final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();

            HttpMessageConverter<?>[] httpMessageConverter = { mappingJacksonHttpMessageConverter };

            String[] supportedHttpMethods = { "POST", "GET", "HEAD" };

            annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
            annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);

            return annotationMethodHandlerAdapter;
        }*/
}

The Initializer class is below:
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class Initializer implements WebApplicationInitializer {

    private static final String DISPATCHER_SERVLET_NAME = "dispatcher";

    public void onStartup(ServletContext servletContext)
            throws ServletException {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(WebAppConfig.class);
        servletContext.addListener(new ContextLoaderListener(ctx));

        ctx.setServletContext(servletContext);

        Dynamic servlet = servletContext.addServlet(DISPATCHER_SERVLET_NAME,
                new DispatcherServlet(ctx));
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);
    }

}

I added the following to the web.xml file
<context-param>
    <param-name>quartz:shutdown-on-unload</param-name>
    <param-value>true</param-value>
</context-param>
<context-param>
    <param-name>quartz:wait-on-shutdown</param-name>
    <param-value>false</param-value>
</context-param>
<context-param>
    <param-name>quartz:start-scheduler-on-load</param-name>
    <param-value>true</param-value>
</context-param>
<listener>
    <listener-class>com.x.apimanager.scheduler.ContextListener</listener-class>
</listener>

evolving the final web.xml to the below
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <display-name>apimanager</display-name>
        <context-param>
        <param-name>quartz:shutdown-on-unload</param-name>
        <param-value>true</param-value>
    </context-param>
    <context-param>
        <param-name>quartz:wait-on-shutdown</param-name>
        <param-value>false</param-value>
    </context-param>
    <context-param>
        <param-name>quartz:start-scheduler-on-load</param-name>
        <param-value>true</param-value>
    </context-param>
    <listener>
        <listener-class>com.x.apimanager.scheduler.ContextListener</listener-class>
    </listener>
</web-app>

The above did not work even though tomcat started a thread each time it was started.
I then attemped to use the spring @Scheduled annotation to run the job. I added the job tasks to a void method (void ApiExpirationTask()) in my service class and annotated it with @Scheduled and added <task:annotaion-driven></task:annotaion-driven> to the web.xml file.
Service class
import java.io.IOException;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import javax.annotation.Resource;
//import javax.inject.Inject;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.gson.Gson;
import com.x.apimanager.exception.ApiNotFound;
import com.x.apimanager.model.Api;
import com.x.apimanager.repository.ApiRepository;

@Service
public class ApiServiceImpl implements ApiService {

    private static final int PAGE_SIZE = 5;

    //@Inject private ApiRepository apiRepository2;

    @Resource
    private ApiRepository apiRepository;

    public Page<Api> getApiLog(Integer pageNumber) {
        PageRequest request =
            new PageRequest(pageNumber - 1, PAGE_SIZE, Sort.Direction.DESC, "id");
        return apiRepository.findAll(request);
    }

    @Override
    @Transactional
    public Api create(Api api) {
        Api createdApi = api;

        createdApi.setIsExpired(true);

        //getting current date and time using Date class
       //DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
       Date dateobj = new Date();

        //createdApi.setCreatedDate(df.format(dateobj));
        //createdApi.setModifiedDate(df.format(dateobj));

        createdApi.setCreatedDate(dateobj); 
        createdApi.setModifiedDate(dateobj);

        return apiRepository.save(createdApi);
    }

    @Override
    @Transactional
    public Api findById(int id) {
        return apiRepository.findOne(id);
    }

    @Override
    @Transactional(rollbackFor=ApiNotFound.class)
    public Api delete(int id) throws ApiNotFound {
        Api deletedApi = apiRepository.findOne(id);

        if (deletedApi == null)
            throw new ApiNotFound();

        apiRepository.delete(deletedApi);
        return deletedApi;
    }

    @Override
    @Transactional
    public List<Api> findAll() {
        return apiRepository.findAll();
    }

    @Override
    @Transactional(rollbackFor=ApiNotFound.class)
    public Api update(Api api) throws ApiNotFound {
        Api updatedApi = apiRepository.findOne(api.getId());

        if (updatedApi == null)
            throw new ApiNotFound();

        updatedApi.setApiKey(api.getApiKey());
        updatedApi.setUrl(api.getUrl());
        updatedApi.setModifiedDate(new Date());
        return updatedApi;
    }

    @Scheduled(fixedDelay=60000)
    @Transactional
    public void ApiExpirationTask()
    {
        List<Api> apiList = findAll();

        for(Api api : apiList) {
            //System.out.println(api.getApiKey());
            //final SimpleDateFormat df = new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss" );
            final java.util.Calendar cal = GregorianCalendar.getInstance();
            cal.setTime( api.getCreatedDate() );
            cal.add( GregorianCalendar.MINUTE, 1 ); // date manipulation
            Date dateExpectedExpiryDate = cal.getTime();
            Date dateNow = new Date();
            if ((dateNow.compareTo(dateExpectedExpiryDate) > 1) || (dateNow.compareTo(dateExpectedExpiryDate) == 0))
            {
                api.setIsExpired(true);

                try {
                    update(api);
                    System.out.println("Updating API");

                    //post the entity as JSON
                    String postUrl=api.getUrl();// put in your url
                    Gson gson= new Gson();
                    HttpPost post = new HttpPost(postUrl);
                    StringEntity  postingString = new StringEntity(gson.toJson(api));//convert your pojo to   json
                    System.out.println(postingString.toString());
                    post.setEntity(postingString);
                    post.setHeader("Content-type", "application/json");
                    @SuppressWarnings("deprecation")
                    HttpClient httpClient = new DefaultHttpClient();
                    HttpResponse  response = httpClient.execute(post);
                } catch (ApiNotFound | IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

}

Changes to web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <display-name>apimanager</display-name>
    <task:annotaion-driven></task:annotaion-driven>
</web-app>

The second approach also did not work and it is not throwing any exceptions. Trying to step through Eclipse STS IDE did not yield anything because the break point I set was not hit. Please can someone help me with this?
Thank you very much.

A:

I eventually had success using the second approach: using spring scheduling. As I was using a Configuration class for my bean definitions instead of xml namespaces or bean definitions in xml configuration files, I removed the <task:annotaion-driven></task:annotaion-driven> xml namespace from the web.xml file and replaced it with an annotation in my WebAppConfig class, my @Configuration class in this case.
I then proceeded to move the ApiExpirationTask() method from the Service class/layer below to the its Controller class. I removed the @Transactional annotation from the code snippet and fixed some logical control flow issues.
@Service
public class ApiServiceImpl implements ApiService {

The final method with the job tasks/steps that works and is currently declared in a Controller class is
@Scheduled(fixedDelay=60000)
    public void ApiExpirationTask()
    {
        // Beginning of the ApiKeyExpirationJob Workflow
        List<Api> apiList = apiService.findAll();

        for(Api api1 : apiList) {
            //logger.info(api.getApiKey());
            //final SimpleDateFormat df = new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss" );
            final java.util.Calendar cal = GregorianCalendar.getInstance();
            cal.setTime( api1.getCreatedDate() );
            cal.add( GregorianCalendar.MINUTE, 1 ); // date manipulation
            Date dateExpectedExpiryDate = cal.getTime();
            Date dateNow = new Date();
            if ((dateNow.compareTo(dateExpectedExpiryDate) >= 0) && (api1.getIsExpired() == false))
            {
                api1.setIsExpired(true);

                try {
                    //TODO: Fix apache commons logging to work with SLF4J. Then remove the out.println statements.
                    //logger.info("Updating API");
                    System.out.println("Updating API");
                    apiService.update(api1);

                    //post the entity as JSON
                    String postUrl=api1.getUrl();// put in your url
                    Gson gson= new Gson();
                    HttpPost post = new HttpPost(postUrl);
                    StringEntity  postingString = new StringEntity(gson.toJson(api1));//convert your pojo to   json
                    //logger.info("API Entity in json representaton is: " + gson.toJson(api1));
                    System.out.println("API Entity in json representaton is: " + gson.toJson(api1));
                    //logger.info(postingString.toString());
                    post.setEntity(postingString);
                    post.setHeader("Content-type", "application/json");
                    @SuppressWarnings("deprecation")
                    HttpClient httpClient = new DefaultHttpClient();
                    HttpResponse  response = httpClient.execute(post);
                    //logger.info(response.getStatusLine().toString());
                    System.out.println(response.getStatusLine().toString());
                } catch (IOException | ApiNotFound e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        //End of ApiKeyExpirationJob Workflow

The @Configuration class
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan("com.x.apimanager")
@PropertySource("classpath:application.properties")
@EnableJpaRepositories("com.x.apimanager.repository")
@EnableScheduling
public class WebAppConfig {

As the Controller class that I moved the ApiExpirationTask() method to, has the @Controller annotation, which is a specialization of the generic @Component annotation, the already existing @ComponentScan annotation on the WebAppConfig class (@Configuration class), does the job of scanning and the location the ApiController class which contains the task that should be executed at intervals. If the hosting class is not annotated with @Component or any of its specializations such as @Controller, then we would need to declare an @Bean method in WebAppConfig class to enable detection of the task that has been annotated with @Scheduled in the container or application server.
You can get more information from the following resources.
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableScheduling.html
http://javapapers.com/spring/spring-component-service-repository-controller-difference/
Thank you very much.

