Sunday, September 9, 2012

Simple Spring Quartz Web App with Maven and Eclipse

Update: 08/03/2016 - There is much easier way these days to quickly set up a scheduled process in your Java/Spring app - checkout the "@Scheduled" Spring annotation. The steps below could still be of help to you, if you are stuck with an older Spring version, which does not support the @Scheduled annotation.

1. Create a Maven Web App project with Eclipse

File -> New -> Project -> Other -> Maven Project ->




Next -> Next ->

You should be at the Select Archtype Screen.




Type "webapp" (without the quotes) in the "filter" textbox.
Select the archtype with group Id: org.apache.maven.archtypes
and artifact id: maven-archtype-webapp.

Next -> Type whatever floats your boat for you Group Id and Artifact Id on the next screen:



-> Finish

2. Add needed dependencies to pom.xml. 



You are going to need all the listed dependencies, here is my pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>boyko</groupId>
  <artifactId>batch-example</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>batch-example Maven Webapp</name>
  <url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.opensymphony.quartz</groupId>
<artifactId>quartz</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>3.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
</dependency>
      <dependency>
          <groupId>commons-collections</groupId>
          <artifactId>commons-collections</artifactId>
          <version>3.2.1</version>
      </dependency>
</dependencies>
  <build>
    <finalName>batch-example</finalName>
  </build>
</project>

3. Add Spring to your web app.

Add Spring's ContextLoaderListener and the contextConfigLocation to web.xml.



This is my web.xml:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

</web-app>

4. Create applicationContext.xml under src/main/resources

Right click on the project -> New -> Other -> XML file



5. Create src/main/java source folder

Right click on the project -> New -> Other -> Source Folder

6. Create the job

   Create a package under src/main/java
 
   Create a class that would be your Spring Batch job.


 
   Here is what mine looks like:
    

package boyko;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

public class SampleJob {

public void sampleJobMethod() {

DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");

System.out.println("Invoked on " + dateFormat.format(System.currentTimeMillis()));
}
}


7. Add all needed configuration in applicationContext.xml

The final version of my specific applicationContext.xml is at the end of this section, but here are the additions step-by-step

7.1. Add the Job

<bean id="sampleJob" class="boyko.SampleJob" />

7.2. Create a Job Spring Quartz Bean and associate it with the Job and the Job method


<bean id="sampleJobBean"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="sampleJob" />
<property name="targetMethod" value="sampleJobMethod" />
</bean>

7.3. Create a trigger

<bean id="sampleJobTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="jobDetail" ref="sampleJobBean" />
<property name="repeatInterval" value="10000" />
<property name="startDelay" value="3000" />
</bean>

7.4. Create a scheduler and associate it with the Job Bean and the Trigger

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobDetails">
<list>
<ref bean="sampleJobBean" />
</list>
</property>
<property name="triggers">
<list>
<ref bean="sampleJobTrigger" />
</list>
</property>
</bean>

The numbers in trigger mean that the job will run for first time 3 seconds after app starts, then it will run every 10 seconds.

Here is the entire applicationContext.xml:

<?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">

<bean id="sampleJob" class="boyko.SampleJob" />

<bean id="sampleJobBean"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="sampleJob" />
<property name="targetMethod" value="sampleJobMethod" />
</bean>

<bean id="sampleJobTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="jobDetail" ref="sampleJobBean" />
<property name="repeatInterval" value="10000" />
<property name="startDelay" value="3000" />
</bean>

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobDetails">
<list>
<ref bean="sampleJobBean" />
</list>
</property>
<property name="triggers">
<list>
<ref bean="sampleJobTrigger" />
</list>
</property>
</bean>

</beans>

That should be that.

Run it on whatever server you prefer( I run on Tomcat 7 - right click on project -> Run On Server -> Tomcat 7) and you should see the sysouts in the console:



Attached is the sample. You could import it as an eclipse project after you unzip it and give a try on your own.

boyko-spring-batch-example

And here is an identical example which uses Cron Trigger and JobDetailBean:

boyko-cron-jobDetailBean-example


Update:12-11-2013: There's a better way to do a scheduled job these days. Use the @Scheduled annotation.

Friday, May 11, 2012

Working on one of our Grails apps I got "could not execute query; SQL [select contrac0_.contract_hrs as col_0_0_ from contract contrac0_ ]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute query". I got the exception while running an integration test against the Service class executing the above query.
Farther down the stack there was also "Caused by: java.sql.SQLException: Table not found in statement [select top ? contrac1_.contract_hrs as col_0_0_ from contract contract0_]"

We are using Oracle and the specific domain class had a field defined as Double, but it also had 'sqlType' for the field specified as 'NUMBER(7,2)' to match up with the DDL for the table and to get around the dbCreate set to validate in our DataSource.groovy.

class Contract {
      Double contract_hrs

    static mapping ={
        contract_hrs column: "contract_hours", sqlType: "NUMBER(7,2)"
    }
}

To take care of the test, we had to take out the sqlType out.

Fixing the test this way though, made the application not run. At application startup we started getting an error message saying something along the lines of "expected double, but found integer" for the the contract_hours field(column).

The way to get around that was to change the dbCreate in DataSource.groovy from "validate" to "none".

Hope this helps someone!

Tuesday, April 3, 2012

zip by latitude and longitude with geonames and groovy

The titles says it. Geonames is publicly available RESTful web services provider. One of the services is findNearbyPostalCodesJSON. You give it a latitude and longitude and it gives you back a list of zip codes in JSON format. Try it for my neck of the woods:
 http://ws.geonames.org/findNearbyPostalCodesJSON?formatted=true&lat=39.998&lng=-82.8841

The gotcha is that you can make only up to 2000 service calls per day, after that you are asked to pay and service does not give you back your so desired zip codes.

And here is how we can do it with groovy and HTTPBuilder. You are going to need the following dependencies, assuming a maven project:


<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.1</version>
</dependency>
<dependency>
  <groupId>org.codehaus.groovy.modules.http-builder</groupId>
  <artifactId>http-builder</artifactId>
  <version>0.5.2</version>
</dependency>


And the code:

def http = new HTTPBuilder( 'http://ws.geonames.org' )
http.request( GET, JSON ) {
uri.path = '/findNearbyPostalCodesJSON'
uri.query = [ formatted: true, lat: 39.998, lng: -82.8841 ]
response.success = { resp, json ->
   
   println json
   return json.postalCodes[0].postalCode
 }
 response.failure = { resp ->
 
   log.error("Unexpected error: ${resp.status} : ${resp.statusLine.reasonPhrase}")
   return ""
 }

Hope this is helpful to someone some day.

Cheers!

Sunday, January 8, 2012

Set up ShemaSpy

There shouldn't be any argument that understanding data in your application is important. One tool I have found incredibly valuable in helping me understand database relationships is ShemaSpy. There was already a Hudson job set up on project when I rolled in, so I wouldn't credit myself with discovering SchemaSpy(I guess the credit should go to Paul Mazak), but assuming you already have JAVA installed on your Windows machine to set up SchemaSpy:

1. Download the latest SchemaSpy jar:
http://sourceforge.net/projects/schemaspy/files/

In my case the jar was schemaSpy_5.0.0.jar I saved it as C:\SchemaSpy\schemaSpy_5.0.0.jar

2. Download the latest version of graphviz
http://www.graphviz.org/Download.php

It looks like this one only comes with a msi installer. I'm pretty sure they had a binary at one point, but no longer the case, I guess..

In my case I installed GraphViz to C:\GraphViz_2.28

3. Ensure GraphViz was added to the PATH Environment System variable

In my case the msi installer did append C:\Graphviz_2.28\bin to the PATH

5. Copy to the SchemaSpy directory any jars with the driver that you'd need to connect to your database of choice


In my case since I am going against MySQL so I placed the mysql-connector-java-5.1.13-bin.jar in  C:\SchemaSpy

6. Run SchemaSpy
    Open command prompt and cd to the directory of the SchemaSpy jar
    Run:

java -jar schemaSpy_5.0.0.jar -dp mysql-connector-java-5.1.13-bin.jar -gv "C:\Graphviz_2.28" -hq -t mysql -db my_database_name -host localhost -u my_username -p my_password -o C:\SchemaSpy

7. Open and enjoy the generated graphics

Go to the directory you specified with the -o option of the previous command.
There should be an index.html file there, click on it and ...voila you have graphical representation of your database and its relationships.



Hope this helps someone!