Monday, December 29, 2014

Android dev study notes - 1


  • Dailer
------ code snippet ------
       String number = tv.getText().toString();  // get No from an edittext
    Intent it = new Intent(Intent.ACTION_DIAL);
    it.setData(Uri.parse("tel:"+number));
    this.startActivity(it);

------- permission ------
<uses-permission android:name="android.permission.CALL_PHONE"/>
  • SMS
------ code snippet ------
        String mNo = evMobileNo.getText().toString();
String content = evSMSContent.getText().toString();
SmsManager sm = SmsManager.getDefault();
ArrayList<String> text = sm.divideMessage(content);  // just in case message is too long
for (String s : text) {
sm.sendTextMessage(mNo, null, s, null, null);
}
Toast.makeText(MainActivity.this, "sent success",
Toast.LENGTH_LONG).show();

------- permission ------
 <uses-permission android:name="android.permission.SEND_SMS" />
  • Save/Read File to/from memory card

public void savePrivateMode(String fileName, String fileContent)
throws Exception {
FileOutputStream out = context.openFileOutput(fileName,
Context.MODE_PRIVATE);
out.write(fileContent.getBytes());
out.close();
}

The mode is Context.MODE_PRIVATE, only the app who created the file is allowed to access the file, no one else is allowed to access it.
The file permission is -rw-rw----
If save the same file name again and again, the content will be overwrited.

And the location of this file is /data/data/<app package name>/files/<file name> in the memory card.

public void saveAppendMode(String fileName, String fileContent)
throws Exception {
FileOutputStream out = context.openFileOutput(fileName,
Context.MODE_APPEND);
out.write(fileContent.getBytes());
out.close();
}

The mode is Context.MODE_APPEND, only the app who created the file is allowed to access the file, no one else is allowed to access it.
The file permission is -rw-rw----
If save the same file name again and again, the content will be appended.

And the location of this file is /data/data/<app package name>/files/<file name> in the memory card.

public String read(String fileName) throws Exception {
FileInputStream input = context.openFileInput(fileName);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = input.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
                // omit i/o close() method
return new String(out.toByteArray());
}

This method is allowed to read file under  /data/data/<app package name>/files/<file name> at the same app.

If another app needs to access this file in this app, the code is as follows,

public String read(String fileName) throws Exception {
                String fn = "/data/data/<package name>/files/"+fileName;
                File file = new File(fn);
                // should NOT be  context.openFileInput(fileName); it is for memory card
FileInputStream input = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = input.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
                // omit i/o close() method
return new String(out.toByteArray());
}

If the mode is Context.MODE_PRIVATE or Context.MODE_APPEND, cannot read file from external app, unless the external app set the mode as Context.MODE_WORLD_READABLE when saving the file.
If an app is open the file to be read/write for external apps, when save a file, the file mode need to set as Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE


  • Save/Read File to/from SD card
before read/write file to/from SD card, need to check the card is available or not by the following,

if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))

file location:
File file = new File(Environment.getExternalStorageDirectory(),fileName);

Enviroment.getExternalStorageDirectory() points to /mnt/sdcard for current android os, and points to /sdcard for very older os version.
  • SharedPreferences
let's say user chooses preferred color and font size for UI setting, then save such info to SharedPreferences.

public void save(String color, int fontSize) {
SharedPreferences sf = context.getSharedPreferences("custprefer",
Context.MODE_PRIVATE);
Editor edit = sf.edit();
edit.putString("color", color);
edit.putInt("fontSize", fontSize);
edit.commit();
}

in the end, a new file custprefer.xml will be generated at folder /data/data/<package>/shared_prefs/custprefer.xml  as follows,

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="fontSize" value="12" />
<string name="color">red</string>
</map>

  • SQLite
package com.example.dbdemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper {

public DBHelper(Context context) {
super(context, "demo.db", null, 1);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table person(pid integer primary key autoincrement,name varchar(20))");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

}

From the constructor, set db version is 1, and db file name is demo.db, onCreate() will be triggered if demo.db does not exit.

This is a test class to trigger onCreate() method.
package com.example.dbdemo.test;

import com.example.dbdemo.DBHelper;

import android.test.AndroidTestCase;

public class DBHelperTester extends AndroidTestCase {

public void testDB() throws Exception {
DBHelper helper = new DBHelper(this.getContext());
helper.getWritableDatabase();
}
}

then the new demo.db file will be found in folder /data/data/<package>/databases/demo.db, use the tools sqlite expert to check the table is created successfully.

Now change above code to make the version to 2 in the constructor,and add some db changes in method onUpgrade() which will be triggered if db version changes

package com.example.dbdemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper {

public DBHelper(Context context) {
super(context, "demo.db", null, 2);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table person(pid integer primary key autoincrement,name varchar(20))");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("create table teacher(tid integer primary key autoincrement,name varchar(20))");
db.execSQL("alter table person add address varchar(20) null");
}

}

run above test method again, found a new table and a new field are created.

This is a simple class to insert a record

public class MyDbService {

private DBHelper dbHelper;

public MyDbService(DBHelper dbHelper) {
this.dbHelper = dbHelper;
}

public void save() {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.execSQL("insert into person (name) values (?)",
new Object[] { "testuser" });
}
}
  • TBC

Spring MVC restful tips - 406 error, cannot put email in the url


  • 406 not acceptable error

need to add the following configuration

<mvc:annotation-driven
content-negotiation-manager="contentNegotiationManager">
</mvc:annotation-driven>

<bean id="contentNegotiationManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
<property name="favorParameter" value="true" />
<property name="mediaTypes">
<value>
json=application/json
xml=application/xml
</value>
</property>
</bean>
<!-- resolve not acceptable issue -->
<bean id="jacksonMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>text/xml;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>

this is jackson dependencies:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>${jackson.version}</version>
</dependency>

<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson.version}</version>
</dependency>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<spring.core.version>3.2.4.RELEASE</spring.core.version>
<log.version>1.2.17</log.version>
<hibernate.version>3.6.10.Final</hibernate.version>
<jackson.version>1.9.13</jackson.version>
</properties>


  • avoid URI using dots to be truncated
  @RequestMapping( value = "/users/{email:.+}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE )

Thursday, November 27, 2014

Configure encrypted DB password in spring

If still use XML to configure spring container, we have to put password with plain text in the spring configuration file which is not suitable for production env, this article will mix javaconfig with XML configuration file to use encrypted password in the configuration file.

1. add following bean to spring XML configuration file

<!-- be sure to include the JavaConfig bean post-processor -->
    <bean class="org.springframework.config.java.process.ConfigurationPostProcessor"/>

refer to http://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/ch06.html

2. specify a @configuration class for post processor

<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource" />
<qualifier value="myDS" /> <!-- for multiple datasource -->
</bean>

<tx:annotation-driven transaction-manager="txManager" />
  <!-- this is post processor to retrieve encrypted password and decrypt it -->
<bean class="wx.poc.MyDataSourceConfig" />

3. implement the post processor class to use c3p0

package wx.poc;

import javax.sql.DataSource;
import ..........;

@Configuration
@Import(PropertyPlaceholderConfigurer.class)
public class MyDataSourceConfig
{

    @Value("${jdbc.url}")
    private String databaseUrl;

    @Value("${jdbc.driverClassName}")
    private String driverClass;

    @Value("${jdbc.username}")
    private String user;

    @Value("${jdbc.password}")
    private String password;

    @Value("${c3p0.acqincrement}")
    private int acqIncrement;

    @Value("${c3p0.acqretryattempts}")
    private int acqRetryAttempts;

   // omit other c3p0 settings

    @Bean(name = "myDataSource")
    public DataSource dataSource()
    {
        try
        {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setJdbcUrl( databaseUrl );
            ds.setDriverClass( driverClass );
            ds.setUser( user );
            ds.setPassword( getDecryptedPassword() );

            // pool settings
            ds.setAcquireIncrement( acqIncrement );
            ds.setAcquireRetryAttempts( acqRetryAttempts );
            // omit other setters.

            return ds;
        }
        catch (Exception e)
        {
         }
    }

    private String getDecryptedPassword()
    {
        // decrypt the configured encrypted password
    }
}

4. property file 

# jdbc configuration
jdbc.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc.url=jdbc:sqlserver://<server>:<port>;databaseName=<db name>;
jdbc.username=db_username
jdbc.password=<encrypted password>

c3p0.acqincrement=3
c3p0.acqretryattempts=5
// omit other c3p0 properties

Friday, November 21, 2014

Setup Tectia SSH Server/Client

------
OS: windows XP
Tectia server/client evaluation : download from http://www.ssh.com/index.php/evaluation-downloads.html
------
tectia client installation:
unzip the installation package tectia-client-6.3.0.76-windows-upgrd-eval.zip
run installer file ssh-tectia-client-6.3.0.76-windows.msi with admin permission
select Typical installation until the installation is completed.











The two new icons,'Tectia-SSH Terminal' and 'Tectia-Secur File Transfer', will be shown on the desktop if installation is successful.
------
tectia server installation:
unzip the installation package tectia-server-6.3.0.76-windows-upgrd-eval.zip
double click the installer file ssh-tectia-server-6.3.0.76-windows.msi
select Typical installation until the installation is completed.
reboot computer.
------
make connection to ftp server
double click icon 'Tectia - SSH Terminal' on the desktop
press enter or space key to prompt up 'connect to server' dialogue
input 127.0.0.1 for 'Host Name' and a username for 'User Name'
and select 'Proceed with the connection and save the key for the futuer use.'


















key in password for above account.
------
create connection profile:
select menu Profiles - Add Profile...





key in 'TestProfile' for profile name, 127.0.0.1 for host name

click 'Save' button to save above configuration.

click 'Keys and Certificates' menu on the left navigation, 
click 'New Key...' button on tab 'Keys and Certificates'
click 'Next' button
click 'Upload' button to load the pub key generated.

------
test ftp connection:

the command useage:
> sftpg3 <profile name>    
take note profile name is case-sensitive.
------
use GUI to play FTP
double click 'Tectia - Secure File Transfer' icon on the desktop
select 'TestProfile' to make connection.











select menu 'Window' - 'New File Transfer'

Thursday, August 14, 2014

Table Generator in JPA

use a self-defined table for identity generation on any database, which means it is portable across databases.

1) firstly, create a table to store the id will be generated.

create table tb_id_generator (
tb_name varchar(50) not null primary key,
tb_id int(20) not null
);

insert into tb_id_generator(tb_name,tb_id) values ('table1',1);
insert into tb_id_generator(tb_name,tb_id) values ('table2',1000000);

mysql> select * from tb_id_generator;
+---------+---------+
| tb_name | tb_id   |
+---------+---------+
| table1  |           1  |
| table2  | 1000000 |
+---------+---------+
2 rows in set (0.00 sec)


2) specify strategy in entity object.

------------- PersonEO -------------

package wx.dbdemoc.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.TableGenerator;

@Entity
@Table(name = "person")
public class PersonEO implements Serializable {

private static final long serialVersionUID = 1492053620157953609L;
private Long pId;
private String name;

@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "person_id_gen")
@TableGenerator(name = "person_id_gen", table = "tb_id_generator", pkColumnName = "TB_NAME", valueColumnName = "TB_ID", pkColumnValue = "table1")
public Long getpId() {
return pId;
}

public void setpId(Long pId) {
this.pId = pId;
}

@Column(name = "name")
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

person_id_gen: the name of stragety
tb_id_generator: the table name to store id
table1:  to generate id for entity PersonEO

--------------- Person2EO--------------

package wx.dbdemoc.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.TableGenerator;

@Entity
@Table(name = "person2")
public class Person2EO implements Serializable {

private static final long serialVersionUID = 1492053620157953609L;
private Long pId;
private String name;

@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "person_id_gen2")
@TableGenerator(name = "person_id_gen2", table = "tb_id_generator", pkColumnName = "TB_NAME", valueColumnName = "TB_ID", pkColumnValue = "table2")
public Long getpId() {
return pId;
}

public void setpId(Long pId) {
this.pId = pId;
}

@Column(name = "name")
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

3)  use multiple data source, and multiple threads in single data source to run it, found it is working well in multiple threads

spring + rabbitmq + message-driven consumer

As a message consumer, the way to retrieve the messages from queue via either poll or push.

The is an easy sample how to receive messages via message driven approach, so call "push"

1 app-config.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" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
                http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd">


<rabbit:admin connection-factory="rabbitConnectionFactory" />

<rabbit:connection-factory id="rabbitConnectionFactory"
host="10.10.1.197" port="5672" channel-cache-size="1" username="tester1"
password="tester1" />

<rabbit:queue id="myqueue" name="test.queue1" />

<rabbit:listener-container
connection-factory="rabbitConnectionFactory" concurrency="1" prefetch="1">
<rabbit:listener queue-names="test.queue1" ref="myListener" />
</rabbit:listener-container>

<bean id="myListener" class="wx.mqdemo.MyListener" />
</beans>

2. message listener class

package wx.mqdemo;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

public class MyListener implements MessageListener {

public void onMessage(Message msg) {
byte[] body = msg.getBody();
System.out.println(new String(body));
// if want to roll-back message, throw runtime exception
// throw new RuntimeException("want to rollback tx");
}
}

3. java main

package wx.mqdemo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class LaunchIt {

public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"app-context.xml");
context.start();
}

}


---------------
in order to run this sample, need to prepare a queue named "test.queue1" in rabbitmq, and create a user "tester1"


Thursday, August 7, 2014

spring batch admin - monitor batch jobs

web site : http://docs.spring.io/spring-batch-admin/index.html
download url: http://docs.spring.io/downloads/nightly/release-download.php?project=BATCHADM

decompress the latest version spring-batch-admin-1.3.0.RELEASE.zip

go to the sample folder \spring-batch-admin-1.3.0.RELEASE\sample\spring-batch-admin-sample and run mvn clean install

go to spring-batch-admin-1.3.0.RELEASE\sample\spring-batch-admin-sample\target\spring-batch-admin-sample-1.3.0.RELEASE\WEB-INF\classes\META-INF
remove all sub-folders to remove the sample jobs, such as job1,job2 and infinity

go to spring-batch-admin-1.3.0.RELEASE\sample\spring-batch-admin-sample\target\spring-batch-admin-sample-1.3.0.RELEASE\WEB-INF\classes
remove \org and its all sub-folders to remove the servlets for sample jobs, such as job1,job2 and infinity

remove business-schema-mysql.sql and batch-hsql.properties

copy all the content in batch-mysql.properties to batch-default.properties,then remove file batch-default.properties, and edit jdbc configuraiton to customize your env.

TAKE NOTE:
batch.data.source.init=false
set it to false if batch db and tables are created in advance, otherwise this property=true will drop db first, then create new tables which means all historical data in db will be lost.

copy folder \target\spring-batch-admin-1.3.0.RELEASE to tomecat

copy mysql driver jar to folder %TOMCAT%webapps\spring-batch-admin-sample-1.3.0.RELEASE\WEB-INF\lib

launch tomcat, and go to this url with browse http://localhost:8080/spring-batch-admin-sample-1.3.0.RELEASE/jobs

initially, there is not any jobs, even sample jobs










launch the batch jobs described here













Tuesday, August 5, 2014

configure spring batch as window service

The target to configure this spring batch job demo as window service to run and manage.

1) download java service wrapper from http://wrapper.tanukisoftware.com/doc/english/download.jsp and decompress the zip file

2) use the method3 for integration, refer to http://wrapper.tanukisoftware.com/doc/english/integrate-listener.html

3) in order to integrate the service wrapper, we need a Main class

package wx.batch1;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.tanukisoftware.wrapper.WrapperListener;
import org.tanukisoftware.wrapper.WrapperManager;

public class MyWrapper implements WrapperListener {

private ClassPathXmlApplicationContext context;

/*---------------------------------------------------------------
* Constructors
*-------------------------------------------------------------*/
private MyWrapper() {
}

/**
* Called whenever the native Wrapper code traps a system control signal
* against the Java process. It is up to the callback to take any actions
* necessary. Possible values are: WrapperManager.WRAPPER_CTRL_C_EVENT,
* WRAPPER_CTRL_CLOSE_EVENT, WRAPPER_CTRL_LOGOFF_EVENT, or
* WRAPPER_CTRL_SHUTDOWN_EVENT
*
* @param event
*            The system control signal.
*/
public void controlEvent(int event) {
if ((event == WrapperManager.WRAPPER_CTRL_LOGOFF_EVENT)
&& (WrapperManager.isLaunchedAsService() || WrapperManager
.isIgnoreUserLogoffs())) {
// Ignore
} else {
WrapperManager.stop(0);
// Will not get here.
}

}

/*---------------------------------------------------------------
* WrapperListener Methods
*-------------------------------------------------------------*/
/**
* The start method is called when the WrapperManager is signaled by the
* native Wrapper code that it can start its application. This method call
* is expected to return, so a new thread should be launched if necessary.
*
* @param args
*            List of arguments used to initialize the application.
*
* @return Any error code if the application should exit on completion of
*         the start method. If there were no problems then this method
*         should return null.
*/
public Integer start(String[] arg) {

// load spring configure file and launch it
context = new ClassPathXmlApplicationContext("classpath:app-context.xml");
context.start();
WrapperManager.signalStarting(30000);
return null;
}

/**
* Called when the application is shutting down. The Wrapper assumes that
* this method will return fairly quickly. If the shutdown code code could
* potentially take a long time, then WrapperManager.signalStopping() should
* be called to extend the timeout period. If for some reason, the stop
* method can not return, then it must call WrapperManager.stopped() to
* avoid warning messages from the Wrapper.
*
* @param exitCode
*            The suggested exit code that will be returned to the OS when
*            the JVM exits.
*
* @return The exit code to actually return to the OS. In most cases, this
*         should just be the value of exitCode, however the user code has
*         the option of changing the exit code if there are any problems
*         during shutdown.
*/
public int stop(int exitCode) {
// shutdown spring context
context.registerShutdownHook();

WrapperManager.signalStopping(60000);
return exitCode;
}

/*---------------------------------------------------------------
* Main Method
*-------------------------------------------------------------*/
public static void main(String[] args) {
// Start the application. If the JVM was launched from the native
// Wrapper then the application will wait for the native Wrapper to
// call the application's start method. Otherwise the start method
// will be called immediately.
WrapperManager.start(new MyWrapper(), args);
}
}

4) compile above main class with demo together and generate a jar file batch1.jar

5) create a folder structure as follows,

/bin - copy from step 1
/con
  | - wrapper.conf    //-- copy from step 1
  |- app-context.xml
  |- spring-batch1.xml
/lib
  |- batch1.jar
  |- wrapper.jar   // copy from step 1
  |- wrapper.dll   // copy from step 1
  |- spring-batch-core-2.2.7.RELEASE.jar
  ...... all the other dependencies
/logs   

6) open the wrapper.conf and edit it (the changes are highlighted in red)

#encoding=UTF-8
# Configuration files must begin with a line specifying the encoding
#  of the the file.

#********************************************************************
# Wrapper License Properties (Ignored by Community Edition)
#********************************************************************
# Professional and Standard Editions of the Wrapper require a valid
#  License Key to start.  Licenses can be purchased or a trial license
#  requested on the following pages:
# http://wrapper.tanukisoftware.com/purchase
# http://wrapper.tanukisoftware.com/trial

# Include file problems can be debugged by removing the first '#'
#  from the following line:
##include.debug

# The Wrapper will look for either of the following optional files for a
#  valid License Key.  License Key properties can optionally be included
#  directly in this configuration file.
#include ../conf/wrapper-license.conf
#include ../conf/wrapper-license-%WRAPPER_HOST_NAME%.conf

# The following property will output information about which License Key(s)
#  are being found, and can aid in resolving any licensing problems.
#wrapper.license.debug=TRUE

#********************************************************************
# Wrapper Localization
#********************************************************************
# Specify the locale which the Wrapper should use.  By default the system
#  locale is used.
#wrapper.lang=en_US # en_US or ja_JP

# Specify the location of the Wrapper's language resources.  If these are
#  missing, the Wrapper will default to the en_US locale.
wrapper.lang.folder=../lang

#********************************************************************
# Wrapper Java Properties
#********************************************************************
# Java Application
#  Locate the java binary on the system PATH:
wrapper.java.command=C:/jdk1.6.0_45/bin/java
#  Specify a specific java binary:
#set.JAVA_HOME=/java/path
#wrapper.java.command=%JAVA_HOME%/bin/java

# Tell the Wrapper to log the full generated Java command line.
#wrapper.java.command.loglevel=INFO

# Java Main class.  This class must implement the WrapperListener interface
#  or guarantee that the WrapperManager class is initialized.  Helper
#  classes are provided to do this for you.  See the Integration section
#  of the documentation for details.
wrapper.java.mainclass=wx.batch1.MyWrapper

# Java Classpath (include wrapper.jar)  Add class path elements as
#  needed starting from 1
wrapper.java.classpath.1=../conf
wrapper.java.classpath.2=../lib/wrapper.jar
wrapper.java.classpath.3=../lib/mysql-connector-java-5.1.30.jar
wrapper.java.classpath.4=../lib/spring-batch-core-2.2.7.RELEASE.jar
wrapper.java.classpath.5=../lib/spring-core-3.2.10.RELEASE.jar
wrapper.java.classpath.6=../lib/spring-jdbc-3.2.10.RELEASE.jar
wrapper.java.classpath.7=../lib/batch1.jar
wrapper.java.classpath.8=../lib/spring-context-3.2.10.RELEASE.jar
wrapper.java.classpath.9=../lib/spring-beans-3.2.10.RELEASE.jar
wrapper.java.classpath.10=../lib/commons-logging-1.1.1.jar
wrapper.java.classpath.11=../lib/spring-expression-3.2.10.RELEASE.jar
wrapper.java.classpath.12=../lib/spring-retry-1.0.2.RELEASE.jar
wrapper.java.classpath.13=../lib/spring-tx-3.2.10.RELEASE.jar
wrapper.java.classpath.14=../lib/spring-batch-infrastructure-2.2.7.RELEASE.jar
wrapper.java.classpath.15=../lib/spring-aop-3.2.10.RELEASE.jar
wrapper.java.classpath.16=../lib/aopalliance-1.0.jar
wrapper.java.classpath.17=../lib/xstream-1.3.1.jar
wrapper.java.classpath.18=../lib/org.apache.servicemix.bundles.jettison-1.0.1_1.jar

# Java Library Path (location of Wrapper.DLL or libwrapper.so)
wrapper.java.library.path.1=../lib

# Java Bits.  On applicable platforms, tells the JVM to run in 32 or 64-bit mode.
wrapper.java.additional.auto_bits=TRUE

# Java Additional Parameters
wrapper.java.additional.1=

# Initial Java Heap Size (in MB)
wrapper.java.initmemory=100

# Maximum Java Heap Size (in MB)
wrapper.java.maxmemory=500

# Application parameters.  Add parameters as needed starting from 1
#wrapper.app.parameter.1=

#********************************************************************
# Wrapper Logging Properties
#********************************************************************
# Enables Debug output from the Wrapper.
# wrapper.debug=TRUE

# Format of output for the console.  (See docs for formats)
wrapper.console.format=PM

# Log Level for console output.  (See docs for log levels)
wrapper.console.loglevel=INFO

# Log file to use for wrapper output logging.
wrapper.logfile=../logs/wrapper.log

# Format of output for the log file.  (See docs for formats)
wrapper.logfile.format=LPTM

# Log Level for log file output.  (See docs for log levels)
wrapper.logfile.loglevel=INFO

# Maximum size that the log file will be allowed to grow to before
#  the log is rolled. Size is specified in bytes.  The default value
#  of 0, disables log rolling.  May abbreviate with the 'k' (kb) or
#  'm' (mb) suffix.  For example: 10m = 10 megabytes.
wrapper.logfile.maxsize=0

# Maximum number of rolled log files which will be allowed before old
#  files are deleted.  The default value of 0 implies no limit.
wrapper.logfile.maxfiles=0

# Log Level for sys/event log output.  (See docs for log levels)
wrapper.syslog.loglevel=NONE

#********************************************************************
# Wrapper General Properties
#********************************************************************
# Allow for the use of non-contiguous numbered properties
wrapper.ignore_sequence_gaps=TRUE

# Do not start if the pid file already exists.
wrapper.pidfile.strict=TRUE

# Title to use when running as a console
wrapper.console.title=Test Wrapper Sample Application

#********************************************************************
# Wrapper JVM Checks
#********************************************************************
# Detect DeadLocked Threads in the JVM. (Requires Standard Edition)
wrapper.check.deadlock=TRUE
wrapper.check.deadlock.interval=10
wrapper.check.deadlock.action=RESTART
wrapper.check.deadlock.output=FULL

# Out Of Memory detection.
# (Ignore output from dumping the configuration to the console.  This is only needed by the TestWrapper sample application.)
wrapper.filter.trigger.999=wrapper.filter.trigger.*java.lang.OutOfMemoryError
wrapper.filter.allow_wildcards.999=TRUE
wrapper.filter.action.999=NONE
#  Ignore -verbose:class output to avoid false positives.
wrapper.filter.trigger.1000=[Loaded java.lang.OutOfMemoryError
wrapper.filter.action.1000=NONE
# (Simple match)
wrapper.filter.trigger.1001=java.lang.OutOfMemoryError
# (Only match text in stack traces if -XX:+PrintClassHistogram is being used.)
#wrapper.filter.trigger.1001=Exception in thread "*" java.lang.OutOfMemoryError
#wrapper.filter.allow_wildcards.1001=TRUE
wrapper.filter.action.1001=RESTART
wrapper.filter.message.1001=The JVM has run out of memory.

#********************************************************************
# Wrapper Email Notifications. (Requires Professional Edition)
#********************************************************************
# Common Event Email settings.
#wrapper.event.default.email.debug=TRUE
#wrapper.event.default.email.smtp.host=<SMTP_Host>
#wrapper.event.default.email.smtp.port=25
#wrapper.event.default.email.subject=[%WRAPPER_HOSTNAME%:%WRAPPER_NAME%:%WRAPPER_EVENT_NAME%] Event Notification
#wrapper.event.default.email.sender=<Sender email>
#wrapper.event.default.email.recipient=<Recipient email>

# Configure the log attached to event emails.
#wrapper.event.default.email.attach_log=TRUE
#wrapper.event.default.email.maillog.lines=50
#wrapper.event.default.email.maillog.format=LPTM
#wrapper.event.default.email.maillog.loglevel=INFO

# Enable specific event emails.
#wrapper.event.wrapper_start.email=TRUE
#wrapper.event.jvm_prelaunch.email=TRUE
#wrapper.event.jvm_start.email=TRUE
#wrapper.event.jvm_started.email=TRUE
#wrapper.event.jvm_deadlock.email=TRUE
#wrapper.event.jvm_stop.email=TRUE
#wrapper.event.jvm_stopped.email=TRUE
#wrapper.event.jvm_restart.email=TRUE
#wrapper.event.jvm_failed_invocation.email=TRUE
#wrapper.event.jvm_max_failed_invocations.email=TRUE
#wrapper.event.jvm_kill.email=TRUE
#wrapper.event.jvm_killed.email=TRUE
#wrapper.event.jvm_unexpected_exit.email=TRUE
#wrapper.event.wrapper_stop.email=TRUE

# Specify custom mail content
wrapper.event.jvm_restart.email.body=The JVM was restarted.\n\nPlease check on its status.\n

#********************************************************************
# Wrapper Windows NT/2000/XP Service Properties
#********************************************************************
# WARNING - Do not modify any of these properties when an application
#  using this configuration file has been installed as a service.
#  Please uninstall the service before modifying this section.  The
#  service can then be reinstalled.

# Name of the service
wrapper.name=mybatch1

# Display name of the service
wrapper.displayname=My Batch1 Wrapper App

# Description of the service
wrapper.description=My Batch1 Wrapper App

# Service dependencies.  Add dependencies as needed starting from 1
wrapper.ntservice.dependency.1=

# Mode in which the service is installed.  AUTO_START, DELAY_START or DEMAND_START
wrapper.ntservice.starttype=AUTO_START

# Allow the service to interact with the desktop.
wrapper.ntservice.interactive=false

7) go to /bin folder and run InstallTestWrapper-NT.bat to install the service to window OS







then you can run/stop the service by right-click the new added service.

Or run TestWrapper.bat to test but not as window service

this is the log sample

INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1cb8deef: startup date [Tue Aug 05 12:30:22 SGT 2014]; root of context hierarchy
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Loading XML bean definitions from class path resource [app-context.xml]
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Loading XML bean definitions from class path resource [spring-batch1.xml]
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.beans.factory.support.DefaultListableBeanFactory registerBeanDefinition
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Overriding bean definition for bean 'batch1': replacing [Generic bean: class [org.springframework.batch.core.configuration.xml.SimpleFlowFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] with [Generic bean: class [org.springframework.batch.core.configuration.xml.JobParserJobFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.beans.factory.support.DefaultListableBeanFactory registerBeanDefinition
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Overriding bean definition for bean 'batch2': replacing [Generic bean: class [org.springframework.batch.core.configuration.xml.SimpleFlowFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] with [Generic bean: class [org.springframework.batch.core.configuration.xml.JobParserJobFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@538d7ace: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,dataSource,jobRepository,transactionManager,jobLauncher,org.springframework.batch.core.scope.internalStepScope,org.springframework.beans.factory.config.CustomEditorConfigurer,org.springframework.batch.core.configuration.xml.CoreNamespacePostProcessor,step1,batch1,step2,batch2,org.springframework.context.annotation.internalAsyncAnnotationProcessor,org.springframework.context.annotation.internalScheduledAnnotationProcessor,org.springframework.scheduling.support.ScheduledMethodRunnable#0,org.springframework.scheduling.config.CronTask#0,org.springframework.scheduling.support.ScheduledMethodRunnable#1,org.springframework.scheduling.config.CronTask#1,org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar#0,helloTasklet,helloTasklet2,launch1,launch2,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Loaded JDBC driver: com.mysql.jdbc.Driver
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.batch.core.launch.support.SimpleJobLauncher afterPropertiesSet
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: No TaskExecutor has been set, defaulting to synchronous executor.
INFO   | jvm 1    | 2014/08/05 12:30:24 | Aug 5, 2014 12:30:24 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:24 | INFO: Job: [FlowJob: [name=batch2]] launched with the following parameters: [{time=1407213024002}]
INFO   | jvm 1    | 2014/08/05 12:30:24 | Aug 5, 2014 12:30:24 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO   | jvm 1    | 2014/08/05 12:30:24 | INFO: Executing step: [step2]
INFO   | jvm 1    | 2014/08/05 12:30:24 | Hello World22222 Tue Aug 05 12:30:24 SGT 2014
INFO   | jvm 1    | 2014/08/05 12:30:24 | Aug 5, 2014 12:30:24 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:24 | INFO: Job: [FlowJob: [name=batch2]] completed with the following parameters: [{time=1407213024002}] and the following status: [COMPLETED]
INFO   | jvm 1    | 2014/08/05 12:30:25 | Aug 5, 2014 12:30:25 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:25 | INFO: Job: [FlowJob: [name=batch1]] launched with the following parameters: [{time=1407213025001}]
INFO   | jvm 1    | 2014/08/05 12:30:25 | Aug 5, 2014 12:30:25 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO   | jvm 1    | 2014/08/05 12:30:25 | INFO: Executing step: [step1]
INFO   | jvm 1    | 2014/08/05 12:30:25 | Hello World Tue Aug 05 12:30:25 SGT 2014
INFO   | jvm 1    | 2014/08/05 12:30:25 | Aug 5, 2014 12:30:25 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:25 | INFO: Job: [FlowJob: [name=batch1]] completed with the following parameters: [{time=1407213025001}] and the following status: [COMPLETED]
INFO   | jvm 1    | 2014/08/05 12:30:30 | Aug 5, 2014 12:30:30 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:30 | INFO: Job: [FlowJob: [name=batch1]] launched with the following parameters: [{time=1407213030002}]
INFO   | jvm 1    | 2014/08/05 12:30:30 | Aug 5, 2014 12:30:30 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO   | jvm 1    | 2014/08/05 12:30:30 | INFO: Executing step: [step1]
INFO   | jvm 1    | 2014/08/05 12:30:30 | Hello World Tue Aug 05 12:30:30 SGT 2014
INFO   | jvm 1    | 2014/08/05 12:30:30 | Aug 5, 2014 12:30:30 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:30 | INFO: Job: [FlowJob: [name=batch1]] completed with the following parameters: [{time=1407213030002}] and the following status: [COMPLETED]
INFO   | jvm 1    | 2014/08/05 12:30:32 | Aug 5, 2014 12:30:32 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:32 | INFO: Job: [FlowJob: [name=batch2]] launched with the following parameters: [{time=1407213032002}]
INFO   | jvm 1    | 2014/08/05 12:30:32 | Aug 5, 2014 12:30:32 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO   | jvm 1    | 2014/08/05 12:30:32 | INFO: Executing step: [step2]
INFO   | jvm 1    | 2014/08/05 12:30:32 | Hello World22222 Tue Aug 05 12:30:32 SGT 2014
INFO   | jvm 1    | 2014/08/05 12:30:32 | Aug 5, 2014 12:30:32 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:32 | INFO: Job: [FlowJob: [name=batch2]] completed with the following parameters: [{time=1407213032002}] and the following status: [COMPLETED]
INFO   | jvm 1    | 2014/08/05 12:30:35 | Aug 5, 2014 12:30:35 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:35 | INFO: Job: [FlowJob: [name=batch1]] launched with the following parameters: [{time=1407213035001}]
INFO   | jvm 1    | 2014/08/05 12:30:35 | Aug 5, 2014 12:30:35 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO   | jvm 1    | 2014/08/05 12:30:35 | INFO: Executing step: [step1]
INFO   | jvm 1    | 2014/08/05 12:30:35 | Hello World Tue Aug 05 12:30:35 SGT 2014
INFO   | jvm 1    | 2014/08/05 12:30:35 | Aug 5, 2014 12:30:35 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:35 | INFO: Job: [FlowJob: [name=batch1]] completed with the following parameters: [{time=1407213035001}] and the following status: [COMPLETED]
STATUS | wrapper  | 2014/08/05 12:30:35 | <-- Wrapper Stopped

Spring scheduler launches multiple jobs

1. spring configuration file app-context.xml

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

<context:component-scan base-package="wx.batch1" />

<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/testdb2" />
<property name="username" value="tester2" />
<property name="password" value="abcd1234" />
</bean>
       <!-- use mysql to store spring batch meta data -->
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseType" value="mysql" />
</bean>

<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>

<import resource="classpath:spring-batch1.xml" />
       <!-- configure spring scheduler -->
<task:annotation-driven />
<task:scheduled-tasks>
<task:scheduled ref="launch1" method="runIt" cron="0/5 * * * * *"></task:scheduled>
<task:scheduled ref="launch2" method="runIt" cron="0/8 * * * * *"></task:scheduled>
</task:scheduled-tasks>
</beans>


in order to store sprint batch meta data, need to create tables provided by spring framework, which can be found in spring-batch-core-<version>.RELEASE.jar, they are schema-drop-mysql.sql and schema-mysql.sql

take note, some indexes need to be added in database manually, refer to B.10 at http://docs.spring.io/spring-batch/2.2.x/reference/html/metaDataSchema.html

2) spring batch configuration file spring-batch1.xml

<beans:beans xmlns="http://www.springframework.org/schema/batch"
xmlns:beans="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.xsd
           http://www.springframework.org/schema/batch
           http://www.springframework.org/schema/batch/spring-batch-2.2.xsd">

<job id="batch1" job-repository="jobRepository" restartable="true">
<step id="step1">
<tasklet ref="helloTasklet" />
</step>
</job>

<job id="batch2" job-repository="jobRepository" restartable="true">
<step id="step2">
<tasklet ref="helloTasklet2" />
</step>
</job>

</beans:beans>


3) java code to launch job batch1

package wx.batch1;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("launch1")
public class Launch1 {

@Autowired
@Qualifier("jobLauncher")
private JobLauncher jobLauncher;
@Autowired
@Qualifier("batch1")
private Job job;

public void runIt() {
JobParameters jobParameters = new JobParametersBuilder().addLong(
"time", System.currentTimeMillis()).toJobParameters();
try {
JobExecution execution = jobLauncher.run(job, jobParameters);
} catch (Exception e) {

e.printStackTrace();
}
}

public JobLauncher getJobLauncher() {
return jobLauncher;
}

public void setJobLauncher(JobLauncher jobLauncher) {
this.jobLauncher = jobLauncher;
}

public Job getJob() {
return job;
}

public void setJob(Job job) {
this.job = job;
}

}

4) tasklet for job batch1

package wx.batch1;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Service;

@Service("helloTasklet")
public class HelloWorldTasklet implements Tasklet {

public RepeatStatus execute(StepContribution arg0, ChunkContext arg1)
throws Exception {
System.out.println("Hello World " + new java.util.Date());
return RepeatStatus.FINISHED;
}

}


5) java code to launch job batch2

package wx.batch1;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("launch2")
public class Launch2 {

@Autowired
@Qualifier("jobLauncher")
private JobLauncher jobLauncher;
@Autowired
@Qualifier("batch2")
private Job job;

public void runIt() {
JobParameters jobParameters = new JobParametersBuilder().addLong(
"time", System.currentTimeMillis()).toJobParameters();
try {
JobExecution execution = jobLauncher.run(job, jobParameters);
} catch (Exception e) {

e.printStackTrace();
}
}

public JobLauncher getJobLauncher() {
return jobLauncher;
}

public void setJobLauncher(JobLauncher jobLauncher) {
this.jobLauncher = jobLauncher;
}

public Job getJob() {
return job;
}

public void setJob(Job job) {
this.job = job;
}

}


6) tasklet for batch2

package wx.batch1;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Service;

@Service("helloTasklet2")
public class HelloWorldTasklet2 implements Tasklet {

public RepeatStatus execute(StepContribution arg0, ChunkContext arg1)
throws Exception {
System.out.println("Hello World22222 " + new java.util.Date());
return RepeatStatus.FINISHED;
}

}


7) launch class

package wx.batch1;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class LaunchScheduler {

public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"app-context.xml");
context.start();
}

}