Archive Page 2

21
Feb
16

Changing Font Size in Eclipse Debian Jessie

Tried to change the font size of the File explorer on the left side but didnt have any luck until I tried the following:

create a file in your home directory.

vi .gtkrc-eclipse

style "eclipse" {
        font_name = "Sans Condensed 9"
}
class "GtkWidget" style "eclipse"

Save and run eclipse by typing the following:

GTK2_RC_FILES=/usr/share/themes/Clearlooks/gtk-2.0/gtkrc:$HOME/.gtkrc-eclipse “$HOME/.local/usr/local/eclipse-mars/eclipse”

Replace the path to your eclipse install.

20
Feb
16

using JPA 2.x with spring 3.x transactions

This page demonstrates how to use JPA EntityManager within a Spring Transaction using HSQLDB a Java in memory database. We will use a Spring DataManager responsible for database interaction, Spring service class for transaction management.

This is a update for a blog post I written back in 2010. That post dealt with the same topic using plain JdbcTemplate.

Highlights

  • Spring Pure Annotation based configuration (ZERO XML files) with an exception of the pom.xml file
  • Java JPA
  • HSQLDB

Background

Based on the default behavior, when the application throws a run time exception any method that is defined to be transactional issues a roll-back on whatever resource is currently participating in the transaction. To define a method as transactional just put an @Transactional annotation before it.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Requirements

  • Java 8

Implementation

The following example will demonstrate the transaction capabilities of the spring framework. Consider it a very basic “hello world” program for transactional code.

Create the pom.xml

vi pom.xml

<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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.test</groupId>
	<artifactId>springJPATXTest</artifactId>
	<packaging>jar</packaging>
	<version>20160220</version>
	<url>http://maven.apache.org</url>
	
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>
		
	<properties>
		<spring.version>4.1.4.RELEASE</spring.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>

		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-jpa</artifactId>
			<version>1.7.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.3.3</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate.javax.persistence</groupId>
			<artifactId>hibernate-jpa-2.1-api</artifactId>
			<version>1.0.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>4.3.7.Final</version>
		</dependency>		
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>

			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>

		</plugins>
	</build>	
</project>

The following class uses Spring Annotation based configuration that sets up the data source and transaction manager.

vi src/main/java/com/test/SpringConfig.java

package com.test;

import java.util.Properties;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan("com.test")
@EnableJpaRepositories("com.test")
@EnableTransactionManagement
public class SpringConfig {

	@Bean
	public DataSource getDataSource() {
		DriverManagerDataSource dm = new DriverManagerDataSource();
		dm.setDriverClassName("org.hsqldb.jdbcDriver");
		// change the "/tmp" to be another location on your system.
		dm.setUrl("jdbc:hsqldb:file:/tmp/springTXTestDB;shutdown=true");
		dm.setUsername("sa");
		return dm;
	}

	@Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
		LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
		em.setDataSource(getDataSource());
		em.setPackagesToScan(new String[] { "com.test" });
		JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
		em.setJpaVendorAdapter(vendorAdapter);
		em.setJpaProperties(additionalProperties());
		return em;
	}

	@Bean
	public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManager.setEntityManagerFactory(emf);
		return transactionManager;
	}

	@Bean
	public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
		return new PersistenceExceptionTranslationPostProcessor();
	}

	Properties additionalProperties() {
		Properties properties = new Properties();
		properties.setProperty("hibernate.flushMode", "FLUSH_AUTO");
		properties.setProperty("hibernate.hbm2ddl.auto", "update");
		properties.setProperty("hibernate.show_sql", "true");
		return properties;
	}
}

The following is just an interface.

vi src/main/java/com/test/TestTransactionDataManager.java

package com.test;

public interface TestTransactionDataManager {
	public abstract Number writeData(String data);
}

vi src/main/java/com/test/TestTransactionDataManagerImpl.java

package com.test;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;

/**
 * This data manager is used to test the transactional capabilities
 * of the spring framework. The code below is not marked as transactional
 * however it participates in one when called from a transactional method.
 */
@Repository("testTransactionDataManager")
public class TestTransactionDataManagerImpl implements TestTransactionDataManager {

	@PersistenceContext 
	private EntityManager em;

	/**
	 * Responsible for writing data to a transactional resource.
	 * @param data
	 */
	public Number writeData(String data) {
		DBRecord record = new DBRecord();
		record.setData(data);
    	em.persist(record);
    	em.flush();
    	return record.getId();
	}
}

Just an interface…

vi src/main/java/com/test/TestTransactionModel.java

package com.test;

public interface TestTransactionModel {
	void writeDataWithOutTx(String message);
	void writeDataWithMultipleCalls(String message);
	void writeDataWithTx(String message);
	void writeDataWithTxSuccess();
}

This is our Model class. The model layer is a natural place to define unit of work. Model methods are called from “control layer” in the MVC architecture. (Struts actions, SpringMVC Command, or Servlets) THERE SHOULD BE AT MOST ONE call to a transaction enabled model method to get a the “unit of work” behavior. Otherwise if you have more than one call to these methods and one succeeds and the other fails you will not get the “unit of work” behavior. Please keep this in mind… Only one call to a transactional method from the “control layer”.

vi src/main/java/com/test/TestTransactionModelImpl.java

package com.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("testTransactionModel")
public class TestTransactionModelImpl implements TestTransactionModel {

	@Autowired
	private TestTransactionDataManager testTransactionDataManager;
	
	@Transactional
	@Override
	public void writeDataWithTxSuccess() {
		testTransactionDataManager.writeData("Writing data within a transactional method. You WILL see this record.");
	}

	public void writeDataWithOutTx(String message) {
		testTransactionDataManager.writeData("Writing data within a NON transactional method. You WILL NOT see this record.");
	}

	/**
	 * This method will throw a runtime exception and the data will be rolled
	 * back.
	 */
	@Transactional  // remember @Transaction for public methods only!
	public void writeDataWithTx(String message) {
		if(message == null) {
			message = "Writing data within a transactional method with an exception. You will NOT see this record.";
		}
		Number number = testTransactionDataManager.writeData(message);
		System.out.println("wrote item: " + number);
		throw new RuntimeException();
	}

	/**
	 * This method tests the capability of calling another method that is
	 * declared to be transactional where the second method throws an exception
	 * but this method still commits. This is important to know that calling nested
	 * transactional methods will not result in a commit.
	 */
	@Transactional
	public void writeDataWithMultipleCalls(String message) {
		String msg = "Suppressing runtimeexception and committing anyway you will see this record.";
		try {
			writeDataWithTx(msg);
		} catch(RuntimeException ex) {
			System.out.println(msg);
		}
	}
}

The following is the JPA Entity we will be writing to the database.
vi src/main/java/com/test/DBRecord.java

package com.test;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="test_table")
public class DBRecord {
	@Id
	@GeneratedValue
	private Integer id;
	
	private String data;	
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getData() {
		return data;
	}
	public void setData(String data) {
		this.data = data;
	}
	@Override
	public String toString() {
		return "DBRecord [id=" + id + ", data=" + data + "]";
	}
}

The App.java JUnit Test doIt() method will call methods on the model layer.

vi src/test/java/com/test/App.java

package com.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
//	private ApplicationContext context = new ClassPathXmlApplicationContext("com/test/applicationContext.xml");
	static private ApplicationContext context = null;
	
	static {
		context = new AnnotationConfigApplicationContext(com.test.SpringConfig.class);
	}
		
	@Test
	public void doIt() {
		TestTransactionModel model = (TestTransactionModel) context.getBean("testTransactionModel");
		
		try {
			model.writeDataWithTxSuccess();
		} catch(Exception ex) {
		}
		try {
			model.writeDataWithOutTx(null);
		} catch (RuntimeException ex) { // do nothing
		}

		try {
			model.writeDataWithTx(null);
		} catch (RuntimeException ex) { // do nothing
		}

		try {
			model.writeDataWithMultipleCalls(null);
		} catch (RuntimeException rex) { // do nothing
		}
	}

	public App() {
		doIt();
	}


}

Results

The results get saved to the HSQLDB database location that is specified in the SpringConfig.java file. It defaults to a file in the /tmp dir. Open the .script file and you will find the data saved to the table towards the bottom.

15
Feb
16

Managed Bean scope for a AJAX JSF Page @RequestScoped vs @ViewScoped

This page will demonstrate the difference between a @RequestScope and @ViewScoped managed bean in a AJAX enabled JSF application.

Background

The @RequestScoped bean lives as long as a single HTTP request-response cycle. This includes AJAX request and responses. At the end of the cycle the bean will be no longer available.

The @ViewScoped managed bean lives as long as you are on the same view like action calls with post-backs that don’t result in the page changing. This includes actions that result in output being sent to another component on the page.

To demonstrate the difference between @ViewScoped and @RequestScoped we will define 2 managed beans each with a different Scope and use them within a AJAX enabled JSF page. We will use the Java EE @PostConstruct and @PreDestroy annotations to print out when each bean is created and destroyed.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Requirements

  • Java 8 or above
  • Maven 3 or above
  • Tomcat 7

Create the pom.xml

vi pom.xml

<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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.test</groupId>
	<artifactId>JSFTest</artifactId>
	<version>20160215</version>
	<packaging>war</packaging>
	<name>JSFTest</name>

	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>
	
	<repositories>
		<repository>
			<id>prime-repo</id>
			<name>PrimeFaces Maven Repository</name>
			<url>http://repository.primefaces.org</url>
			<layout>default</layout>
		</repository>
	</repositories>

	<dependencies>
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.glassfish</groupId>
			<artifactId>javax.faces</artifactId>
			<version>2.1.6</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>3.0-alpha-1</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jsp-api</artifactId>
			<version>2.0</version>
		</dependency>

		<dependency>
			<groupId>net.bootsfaces</groupId>
			<artifactId>bootsfaces</artifactId>
			<version>0.8.0</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.primefaces</groupId>
			<artifactId>primefaces</artifactId>
			<version>5.3</version>
		</dependency>
		<dependency>
			<groupId>org.primefaces.themes</groupId>
			<artifactId>bootstrap</artifactId>
			<version>1.0.9</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
						
		</plugins>
	</build>

</project>

The following is the Request Scoped Managed bean.

vi src/main/java/com/test/AddressBeanRequestScoped.java

package com.test;
 
import java.io.Serializable;
 
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.RequestScoped;
 
@ManagedBean
@RequestScoped
public class AddressBeanRequestScoped implements Serializable {
 
    private static final long serialVersionUID = 1L;
     
    private Long id;
    private String name;
    private String address;
    private String city;
    private String state;
    private String zip;
 
    @PostConstruct
    void init() {
        System.out.println("Initializing " + this.getClass().getName());
    }
     
    @PreDestroy
    void destroy() {
        System.out.println("Destroying " + this.getClass().getName());
    }
 
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void handleKeyEvent() {
        address = address.toUpperCase();
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    public String getZip() {
        return zip;
    }
    public void setZip(String zip) {
        this.zip = zip;
    }   
}

The following is the View Scoped Managed bean.

vi src/main/java/com/test/AddressBeanViewScoped.java

package com.test;
 
import java.io.Serializable;
 
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
 
@ManagedBean
@ViewScoped
public class AddressBeanViewScoped implements Serializable {
 
    private static final long serialVersionUID = 1L;
     
    private Long id;
    private String name;
    private String address;
    private String city;
    private String state;
    private String zip;
 
    @PostConstruct
    void init() {
        System.out.println("Initializing Address Bean.");
    }
     
    @PreDestroy
    void destroy() {
        System.out.println("Destroying Address Bean.");
    }
 
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void handleKeyEvent() {
        address = address.toUpperCase();
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    public String getZip() {
        return zip;
    }
    public void setZip(String zip) {
        this.zip = zip;
    }   
}

The following AJAX enabled JSF page uses both beans.
vi src/main/webapp/ajaxExample.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:b="http://bootsfaces.net/ui"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:p="http://primefaces.org/ui">
<h:head>
    <title>BootsFaces Basic Page</title>
</h:head>
<h:body>
    <b:container>
        <b:navBar brand="JSF Demo" brandHref="index.xhtml" inverse="true">
            <b:navbarLinks>
                <b:navLink value="More about Ajax"
                    href="http://www.primefaces.org/showcase/ui/ajax/basic.xhtml"></b:navLink>
            </b:navbarLinks>
        </b:navBar>
        <h:form>
 
            <b:panel title="Ajax Listener (on keyUp) View Scoped Bean" look="primary">
                <h:outputText value="Keyup: " />
                <p:inputText value="#{addressBeanViewScoped.address}">
                    <p:ajax event="keyup" update="out"
                        listener="#{addressBeanViewScoped.handleKeyEvent}" />
                </p:inputText>
                <h:outputText id="out" value="#{addressBeanViewScoped.address}" />
            </b:panel>
            <b:panel title="Ajax Listener (on keyUp) Request Scoped Bean" look="primary">
                <h:outputText value="Keyup: " />
                <p:inputText value="#{addressBeanRequestScoped.address}">
                    <p:ajax event="keyup" update="out2"
                        listener="#{addressBeanRequestScoped.handleKeyEvent}" />
                </p:inputText>
                <h:outputText id="out2" value="#{addressBeanRequestScoped.address}" />
            </b:panel>
 
        </h:form>
 
    </b:container>
</h:body>
</html>

And finally the web.xml file.

vi src/main/webapp/WEB-INF/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"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	id="WebApp_ID" version="3.0">
	<display-name>JSFTest</display-name>

	<welcome-file-list>
		<welcome-file>ajaxExample.xhtml</welcome-file>
	</welcome-file-list>

	<context-param>
		<param-name>primefaces.THEME</param-name>
		<param-value>bootstrap</param-value>
	</context-param>

	<context-param>
		<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
		<param-value>.xhtml</param-value>
	</context-param>
	<servlet>
		<servlet-name>Faces Servlet</servlet-name>
		<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>Faces Servlet</servlet-name>
		<url-pattern>*.xhtml</url-pattern>
	</servlet-mapping>

</web-app>

Start tomcat 7 and navigate to the following page:
http://localhost:8080/JSFTest/ajaxExample.xhtml

You should be able to view the difference on the eclipse console or Tomcat 7 console log while you type within the text boxes on the JSF page.

14
Feb
16

Log4j configuration with Java CDI (Contexts and Dependency Injection)

This page describes how to configure log4j in a Java CDI stand-alone application.

All of us seen code that looks like this over and over again.

private Logger logger = Logger.getLogger(this.getClass().getName());

We will use Java Contexts and Dependency Injection to create a custom log4j Logger for a service class.

Modify the pom.xml

vi pom.xml

<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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.test</groupId>
	<artifactId>log4jCDI</artifactId>
	<version>20160214</version>

	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>
	
	<dependencies>
		<dependency>
			<groupId>javax.enterprise</groupId>
			<artifactId>cdi-api</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>org.jboss.weld.se</groupId>
			<artifactId>weld-se</artifactId>
			<version>2.2.6.Final</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.8.2</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
		
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

</project>

Setup the log4j configuration files. We will be using a very basic file that outputs to the console.
vi src/main/resources/log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
   
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out"/>
        <param name="Threshold" value="INFO" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %-5p %c - %m%n"/>
        </layout>
    </appender>
    <logger name="com.test" additivity="true">
        <level value="DEBUG"/>
    </logger>
    <root>
        <level value="ERROR"/>
        <appender-ref ref="CONSOLE"/>
    </root> 
</log4j:configuration>

Producer (Factory)

The following CDI Producer creates a log4j Logger whenever it encounters a @Inject. Java keeps track of all the methods that are annotated with @Producer. If it comes across an inject
that requires a type produced by one of these producers then it calls the producer method to get this instance. The producer method here accepts a InjectionPoint metadata object that helps identify the location of where the injection is occuring. We will use this information to create a log4j logger custom to the class where its being injected into.

vi src/main/java/com/test/LoggerProducer.java

package com.test;

import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;

import org.apache.log4j.Logger;

public class LoggerProducer {
	@Produces
	public Logger produceLogger(InjectionPoint injectionPoint) {
		return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
	}
}

Service Class

The following service class is responsible for printing out the different levels of log messages.

vi src/main/java/com/test/TestLogger.java

package com.test;

import javax.inject.Inject;

import org.apache.log4j.Logger;

public class TestLogger {	
	@Inject
	private transient Logger logger;
	
	private static final String THIS_IS = "Method: ";
	
	public void writeLogAllLevels() {
		logger.fatal(THIS_IS + new Object(){}.getClass().getEnclosingMethod().getName());
		logger.error(THIS_IS + new Object(){}.getClass().getEnclosingMethod().getName());
		logger.warn(THIS_IS + new Object(){}.getClass().getEnclosingMethod().getName());
		logger.info(THIS_IS + new Object(){}.getClass().getEnclosingMethod().getName());
		logger.debug(THIS_IS + new Object(){}.getClass().getEnclosingMethod().getName());
	}
}

Junit Test Case

Finally test the logging with a simple junit test case.

vi src/test/java/log4jCDI/TestApp.java

package log4jCDI;

import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;
import org.junit.Test;

import com.test.TestLogger;

public class TestApp {
	static WeldContainer weld=null;
	static {
        weld = new Weld().initialize();
	}
	@Test
	public void test() {
        TestLogger testLogger = weld.instance().select(TestLogger.class).get();
        testLogger.writeLogAllLevels();
	}

}

Log4j xml file

This bare bones log4j file will print all log levels to the console.

vi src/main/resources/log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
   
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out"/>
        <param name="Threshold" value="INFO" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %-5p %c - %m%n"/>
        </layout>
    </appender>
    <logger name="com.test" additivity="true">
        <level value="DEBUG"/>
    </logger>
    <root>
        <level value="ERROR"/>
        <appender-ref ref="CONSOLE"/>
    </root> 
</log4j:configuration>

beans.xml

Finally create a blank beans.xml file in your src/main/java/META-INF folder.

Run the Junit Test Case

At this time you will see output of each of the messages to the console similar to this:

2016-02-14 15:58:18,840 FATAL com.test.TestLogger - Method: writeLogAllLevels
2016-02-14 15:58:18,841 ERROR com.test.TestLogger - Method: writeLogAllLevels
2016-02-14 15:58:18,841 WARN  com.test.TestLogger - Method: writeLogAllLevels
2016-02-14 15:58:18,841 INFO  com.test.TestLogger - Method: writeLogAllLevels
Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.
28
Dec
13

The 3 R’s of Spring Batch 3.0.x with Annotations

This page describes how you can Read, wRrite and perform aRithmetic on flat files using The Spring Batch Framework. We will take a comma separated file (csv) that contain employee information, add some information to it, and write it back to the file system.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

The basic building blocks of any batch process is

  1. Reading a Item
  2. Performing an operation on it
  3. Writing the Item back

Please take some time to review The Domain Language of Batch before proceeding. It covers much of the fundamental concepts we will be covering here.

Batch Steps

This page is focused on an individual step of the batch process.

The following is from the spring batch documentation

A Step is a domain object that encapsulates an independent, sequential phase of a batch job. Therefore, every Job is composed entirely of one or more steps. A Step contains all of the information necessary to define and control the actual batch processing. This is a necessarily vague description because the contents of any given Step are at the discretion of the developer writing a Job. A Step can be as simple or complex as the developer desires. A simple Step might load data from a file into the database, requiring little or no code. (depending upon the implementations used) A more complex Step may have complicated business rules that are applied as part of the processing.

Step Processing types

There are 2 ways a step can process data,

Tasklet

If the step requires only to execute a single task then you can use a tasklet. Typical use case for this is when you need to run a stored procedure, or copy a file from one location to the other. In the “Hello World” example we used a Tasklet to print the message to the console.

Chunk oriented

Chunk oriented processing involves specifying a reader, processor and writer. The input is read one item at a time in sequence and passed to the processor and eventually to the writer in chunks within a transaction boundary. Once the commit interval is reached the items are committed to the writer. Chunk oriented processing is what we will cover on this page.

Library Versions

  • Spring Batch 3.0.0-M3 or above

Input Data

The following is the input csv file that will be read. Please create the following file in the projects resource directory.

vi src/main/resources/input_data.txt

7876,ADAMS,CLERK,1100
7499,ALLEN,SALESMAN,1600
7698,BLAKE,MANAGER,2850
7782,CLARK,MANAGER,2450
7902,FORD,ANALYST,3000
7900,JAMES,CLERK,950
7566,JONES,MANAGER,2975
7839,KING,PRESIDENT,-5000
7654,MARTIN,SALESMAN,1250
7934,MILLER,CLERK,1300
7788,SCOTT,ANALYST,3000
7369,SMITH,CLERK,800
7844,TURNER,SALESMAN,1500
7521,WARD,SALESMAN,1250

Employee Bean

This is a simple bean that represents a single Employee.

vi src/main/java/com/test/Employee.java

package com.test;

public class Employee {

	private Integer empId;
	private String lastName;
	private String title;
	private Integer salary;
	private String rank;
	
	public Integer getEmpId() {
		return empId;
	}
	public void setEmpId(Integer empId) {
		this.empId = empId;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public Integer getSalary() {
		return salary;
	}
	public void setSalary(Integer salary) {
		this.salary = salary;
	}
	public void setRank(String rank) {
		this.rank = rank;
	}
	public String getRank() {
		return rank;
	}
	@Override
	public String toString() {
		return "Employee [empId=" + empId + ", lastName=" + lastName
				+ ", title=" + title + ", salary=" + salary + ", rank=" + rank
				+ "]";
	}	
}

Reading

The reader is configured in the ThreeRJobConfig.java ( see reader() method )

Arithmetic

Not really! All we are doing is assigning a Rank based on the salary amount. The item processor takes an input Bean and converts it to an output bean. In this case the beans are the same but they don’t have to be.

vi src/main/java/com/test/EmployeeProcessor.java

package com.test;

import org.springframework.batch.item.ItemProcessor;

public class EmployeeProcessor implements ItemProcessor<Employee, Employee> {

	public Employee process(Employee emp) throws Exception {
		// if salary >= 2500 then set rank as "Director"		
		if(emp.getSalary() >= 2500 ) {
			emp.setRank("Director");			
		} else {
			emp.setRank("N/A");
		}
		return emp;
	}

}

Writing

The reader is configured in the ThreeRJobConfig.java ( See writer() method )

Job Configuration

vi src/main/java/com/test/config/ThreeRJobConfig.java

package com.test.config;

import java.io.File;

import javax.sql.DataSource;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.file.LineMapper;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor;
import org.springframework.batch.item.file.transform.DelimitedLineAggregator;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;

import com.test.Employee;
import com.test.EmployeeProcessor;

@Configuration
@Import(StandaloneInfrastructureConfiguration.class)
public class ThreeRJobConfig {

	@Autowired
	private JobBuilderFactory jobBuilders;
	
	@Autowired
	private StepBuilderFactory stepBuilders;
	
	@Autowired
	private InfrastructureConfiguration infrastructureConfiguration;
	
	@Autowired
	private DataSource dataSource; // just for show...
	
	@Bean
	public Job threeRJob(){
		return jobBuilders.get("threeRJob")
				.start(step())
				.build();
	}
	
	@Bean
	public Step step(){
		return stepBuilders.get("step")
				.<Employee,Employee>chunk(1)
				.reader(reader())
				.processor(processor())
				.writer(writer())
				.build();
	}

	private ItemWriter<Employee> writer() {
		FlatFileItemWriter<Employee> itemWriter = new FlatFileItemWriter<Employee>();
		DelimitedLineAggregator<Employee> la = new DelimitedLineAggregator<Employee>();
		la.setDelimiter(",");
		BeanWrapperFieldExtractor<Employee> fieldExtractor = new BeanWrapperFieldExtractor<Employee>();
		fieldExtractor.setNames(new String[]{"empId","lastName","title","salary","rank"});
		la.setFieldExtractor(fieldExtractor);
		itemWriter.setLineAggregator(la);
		
		itemWriter.setResource(new FileSystemResource(new File("target/output_data.txt")));
		return itemWriter;
	}

	private ItemProcessor<Employee,Employee> processor() {
		return new EmployeeProcessor();
	}

	private ItemReader<Employee> reader() {
		FlatFileItemReader<Employee> itemReader = new FlatFileItemReader<Employee>();
		itemReader.setLineMapper(lineMapper());
		itemReader.setResource(new ClassPathResource("input_data.txt"));
		return itemReader;
	}

	private LineMapper<Employee> lineMapper() {
		DefaultLineMapper<Employee> lineMapper = new DefaultLineMapper<Employee>();
		DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();
		lineTokenizer.setNames(new String[]{"empId","lastName","title","salary"});
		lineTokenizer.setIncludedFields(new int[]{0,1,2,3});
		BeanWrapperFieldSetMapper<Employee> fieldSetMapper = new BeanWrapperFieldSetMapper<Employee>();
		fieldSetMapper.setTargetType(Employee.class);
		lineMapper.setLineTokenizer(lineTokenizer);
		lineMapper.setFieldSetMapper(fieldSetMapper);
		return lineMapper;
	}
	

}

Execute the job

Go to the command line and type the following:

mvn compile exec:java -Dexec.mainClass=org.springframework.batch.core.launch.support.CommandLineJobRunner -Dexec.args="com.test.config.ThreeRJobConfig threeRJob"

View the Results

The output file will appear in the target/ folder of the project.

Further Reading

To keep things simple we were reading and writing files located in the project own folders. There are many enterprise design patterns that describe the best practices for feeding data into the batch programs. For further reading on this topic please see the Spring Integrations Framework Homepage.

27
Dec
13

Hello World With Spring Batch 3.0.x with Pure Annotations

This page describes how to get a Spring Batch application to print hello world to the console. This page provides a stepping stone to help you get up and running quickly. Since this is a quick and dirty method of getting up and running with spring batch, it does not cover the fundamental concepts. For further information please visit the Spring Batch project documentation site.

This page shows the latest techniques of configuring spring batch using pure java annotations. This results in a significant reduction in work necessary to get a spring batch job running.

An older version of the page is available here. https://numberformat.wordpress.com/2010/02/05/hello-world-with-spring-batch/

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

This page takes about 10 minutes to complete and have a working spring batch application.

Background

The following spring batch example program is the simplest way you could setup a job to run in Spring batch. As such there are some limitations with the following approach.

Use of an In Memory Database

The following program uses an uses in memory database to store information about batch execution runs. This means that there is no protection against duplicate job runs and it does not store when a job was started or completed. Since spring is a pluggable architecture you can always change to use a persistent database like mysql or oracle to store job information. I will describe this process in a future blog entry.

If you use scheduling tools like Autosys you should already have a system that maintains information about job executions. These tools would maintain the history of past runs and if the job succeeded or not etc…so using an in-memory database should not big deal.

Library Versions

  • Spring Batch 3.0.0-M3 or above

Modify the pom.xml

vi pom.xml

<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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.test</groupId>
	<artifactId>spring-batch-helloworld</artifactId>
	<version>20131227</version>
	<name>spring batch hello world</name>
	<packaging>jar</packaging>

	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.github.com/numberformat/20130213/master/repo</url>
		</pluginRepository>
	</pluginRepositories>

	<properties>
		<spring.framework.version>3.2.1.RELEASE</spring.framework.version>
		<spring.batch.version>3.0.0.M2</spring.batch.version>
	</properties>

	<repositories>
		<repository>
			<id>spring-s3</id>
			<name>Spring Maven MILESTONE Repository</name>
			<url>http://maven.springframework.org/milestone</url>
		</repository>
	</repositories>
	<dependencies>
		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>2.6</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-core</artifactId>
			<version>${spring.batch.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-infrastructure</artifactId>
			<version>${spring.batch.version}</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.8.2</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.framework.version}</version>
		</dependency>
		<dependency>
			<groupId>hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>1.8.0.7</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

<!-- Run the application using: 
mvn compile exec:java -Dexec.mainClass=org.springframework.batch.core.launch.support.CommandLineJobRunner -Dexec.args="com.test.config.HelloWorldJobConfig helloWorldJob"
-->
</project>

Setup the log4j configuration files. We will be using a very basic file that outputs to the console.
vi src/main/resources/log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
   
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out"/>
        <param name="Threshold" value="INFO" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %-5p %c - %m%n"/>
        </layout>
    </appender>
    <logger name="org.springframework" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="CONSOLE"/>
    </logger>
    <root>
        <level value="ERROR"/>
        <appender-ref ref="CONSOLE"/>
    </root>
</log4j:configuration>

The job configuration is stored in the com.test.config package. The configuration for the infrastructure and the job is present in this package.

Config Interface

vi src/main/java/com/test/config/InfrastructureConfiguration.java

package com.test.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;

public interface InfrastructureConfiguration {

	@Bean
	public abstract DataSource dataSource();

}

The implementation

vi src/main/java/com/test/config/StandaloneInfrastructureConfiguration.java

package com.test.config;

import javax.sql.DataSource;

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

@Configuration
@EnableBatchProcessing
public class StandaloneInfrastructureConfiguration implements InfrastructureConfiguration {
	
	@Bean
	public DataSource dataSource(){
		EmbeddedDatabaseBuilder embeddedDatabaseBuilder = new EmbeddedDatabaseBuilder();
		return embeddedDatabaseBuilder.addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql")
				.addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql")
				.setType(EmbeddedDatabaseType.HSQL)
				.build();
	}

}

Job Configuration

vi src/main/java/com/test/config/HelloWorldJobConfig.java

package com.test.config;

import javax.sql.DataSource;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import com.test.HelloWorldTasklet;

@Configuration
@Import(StandaloneInfrastructureConfiguration.class)
public class HelloWorldJobConfig {

	@Autowired
	private JobBuilderFactory jobBuilders;
	
	@Autowired
	private StepBuilderFactory stepBuilders;
	
	@Autowired
	private InfrastructureConfiguration infrastructureConfiguration;
	
	@Autowired
	private DataSource dataSource; // just for show...
	
	@Bean
	public Job helloWorldJob(){
		return jobBuilders.get("helloWorldJob")
				.start(step())
				.build();
	}
	
	@Bean
	public Step step(){
		return stepBuilders.get("step")
				.tasklet(tasklet())
				.build();
	}
	
	@Bean
	public Tasklet tasklet() {
		return new HelloWorldTasklet();
	}
}

The Tasklet

This class does the actual printing of the message to the console.

vi src/main/java/com/test/HelloWorldTasklet.java

package com.test;

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;

public class HelloWorldTasklet implements Tasklet {
	 
    public RepeatStatus execute(StepContribution arg0, ChunkContext arg1)
            throws Exception {
        System.out.println("");
        System.out.println(" XXX XXX           XX      XX             ");
        System.out.println("  X   X             X       X             ");
        System.out.println("  X   X             X       X             ");
        System.out.println("  X   X   XXXXX     X       X     XXXXX   ");
        System.out.println("  XXXXX  X     X    X       X    X     X  ");
        System.out.println("  X   X  XXXXXXX    X       X    X     X  ");
        System.out.println("  X   X  X          X       X    X     X  ");
        System.out.println("  X   X  X     X    X       X    X     X  ");
        System.out.println(" XXX XXX  XXXXX   XXXXX   XXXXX   XXXXX   ");
        System.out.println("                                          ");
        System.out.println("                                          ");
        System.out.println("                                          ");
        System.out.println("                                          ");
        System.out.println(" XXX XXX                   XX        XX   ");
        System.out.println("  X   X                     X         X   ");
        System.out.println("  X   X                     X         X   ");
        System.out.println("  X   X   XXXXX  XXX XX     X     XXXXX   ");
        System.out.println("  X X X  X     X   XX  X    X    X    X   ");
        System.out.println("  X X X  X     X   X        X    X    X   ");
        System.out.println("  X X X  X     X   X        X    X    X   ");
        System.out.println("   X X   X     X   X        X    X    X   ");
        System.out.println("   X X    XXXXX  XXXXX    XXXXX   XXXXXX  ");
        System.out.println("");
        return RepeatStatus.FINISHED;
    }
}

Execute the Job

To run the job from the command line type the following.

mvn compile exec:java -Dexec.mainClass=org.springframework.batch.core.launch.support.CommandLineJobRunner -Dexec.args="com.test.config.HelloWorldJobConfig helloWorldJob"

Deploying the application

The following tutorial describes how to Package and deploy the application as a self contained jar. (just be mindful to change the commandline args to the one you see here.)

What’s Next?

In the next few articles I plan on describing how to:

  1. Read and write flat files.
  2. Write header and footer records in the output file.
  3. Replace the in-memory database with a HyperSQL Java database so we can have job information persist between job invocations.
  4. Throw an exception in the middle of a large batch job and restart the job execution from the point where it left off.
  5. Use validation framework like “commons-validator” to perform input file validation and create reject records for manual correction and later processing.
16
Oct
13

Java Debugging using a Custom AlertFrame

This page describes how to create a simple Alert Frame to display java variable during application runtime. It is similar to the alert window commonly displayed in javaScript. The AlertFrame can be used during development as well as in your final application.

An example would be to display the results of a database query you have run, or to display results of a web service call that was made. The AlertFrame has capability to display not only simple wrapper types but also complex beans. It inspects and finds getter methods using reflection and displays the results in the appropriate format. It also allows the user to click further into the bean to explore deeper.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Code

The following is the code for the the AlertFrame.

vi src/main/java/AlertFrame.java

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;


/**
 * Displays List, Properties, and Table data depending on which constructor is
 * called. Constructors for Strings, Array of Strings, Maps of Strings and List
 * of Maps are available.
 */
public class AlertFrame extends JFrame {
	private static final long serialVersionUID = 1L;

	private static final GraphicsDevice gd = GraphicsEnvironment
			.getLocalGraphicsEnvironment().getDefaultScreenDevice();

	private JTextField selectedRowIndex;
	private JTextField selectedColIndex;
	private MapListTableModel mapListTableModel;
	private JTextArea textArea = new JTextArea();
	
	private void initCommon() {
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
	}
	public <T> AlertFrame(List<T> list) {
        initCommon();
		final JTextArea jTextArea = new JTextArea();
		jTextArea.setPreferredSize(new Dimension(300, 300));
		final DefaultListModel listModel = new DefaultListModel();
		int i = 0;
		for (Object value : list) {
			listModel.add(i++, value);
		}
		JList jList = new JList(listModel);
		getContentPane().add(new JScrollPane(jList),
				BorderLayout.CENTER);
		final JScrollPane scrollPane = new JScrollPane(jTextArea);
		getContentPane().add(scrollPane,BorderLayout.SOUTH);
		setSize(gd.getDisplayMode().getWidth() / 2, gd.getDisplayMode()
				.getWidth() / 2);

		jList.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent e) {
				for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
					if (((JList) e.getSource()).isSelectedIndex(i)) {
						jTextArea.setText(String.valueOf(listModel.get(i)));
						scrollPane.invalidate(); // TODO: fix this.
					}
				}
			}
		});
		
		jList.addMouseListener(new MouseAdapter() {
		    public void mouseClicked(MouseEvent evt) {
		        JList list = (JList)evt.getSource();
		        if (evt.getClickCount() == 2) {
		            int index = list.locationToIndex(evt.getPoint());
		            Object obj = listModel.get(index);
		            new AlertFrame(obj);
		        } 
		    }
		});	
		
		showCentered(this);		
	}
	public <T> AlertFrame(Set<T> set) {
		this(new ArrayList<T>(set));
	}
	/**
	 * Tabular data (simple text) with an ordered list of columns.
	 */
	public <T> AlertFrame(List<Map<String, T>> mapList,
			List<String> colNameList) {
        initCommon();
		this.mapListTableModel = new MapListTableModel<T>(mapList, colNameList);

		selectedRowIndex = new JTextField();
		selectedRowIndex.getDocument().addDocumentListener(new DL(this));
		selectedColIndex = new JTextField();
		selectedColIndex.getDocument().addDocumentListener(new DL(this));
		// textArea.setPreferredSize()
		setSize(800, 600);

		JTable jTable = new JTable(mapListTableModel);
		// handle selection events and callbacks here.
		jTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		ListSelectionModel rsm = jTable.getSelectionModel();
		rsm.addListSelectionListener(new SelectionDebugger(selectedRowIndex,
				rsm));

		ListSelectionModel csm = jTable.getColumnModel().getSelectionModel();
		csm.addListSelectionListener(new SelectionDebugger(selectedColIndex,
				csm));

		jTable.setRowSelectionAllowed(false);
		jTable.setColumnSelectionAllowed(false);
		jTable.setCellSelectionEnabled(true);

		getContentPane().add(new JScrollPane(jTable), BorderLayout.CENTER);
		getContentPane().add(new JScrollPane(textArea), BorderLayout.SOUTH);

		pack();
		showCentered(this);
		setVisible(true);
	}
	public AlertFrame(Number value) {
		this(String.valueOf(value));
	}
	public AlertFrame(Object obj) {
        initCommon();
		try {
			Map<String,Object> beanMap = BeanUtils.describe(obj);
		for(String key : beanMap.keySet()) {
			beanMap.put(key, PropertyUtils.getSimpleProperty(obj, key));
		}
			processMap(beanMap);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		}
	}
	public AlertFrame(String value) {
        initCommon();
		textArea.setText(value);
		getContentPane().add(new JScrollPane(textArea));
		setSize(gd.getDisplayMode().getWidth() / 2, gd.getDisplayMode()
				.getWidth() / 2);
		showCentered(this);
	}

	public AlertFrame(String[] values) {
        initCommon();
		final JTextArea jTextArea = new JTextArea();
		jTextArea.setPreferredSize(new Dimension(300, 300));
		final DefaultListModel listModel = new DefaultListModel();
		int i = 0;
		for (Object value : values) {
			listModel.add(i++, String.valueOf(value));
		}
		JList jList = new JList(listModel);
		getContentPane().add(new JScrollPane(jList),
				BorderLayout.SOUTH);
		final JScrollPane scrollPane = new JScrollPane(jTextArea);
		getContentPane().add(scrollPane,BorderLayout.CENTER);
		setSize(gd.getDisplayMode().getWidth() / 2, gd.getDisplayMode()
				.getWidth() / 2);

		jList.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent e) {
//				if (e.getValueIsAdjusting() == false || e.getFirstIndex() == -1) {
//					return;
//				}
				for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
					if (((JList) e.getSource()).isSelectedIndex(i)) {
						jTextArea.setText(String.valueOf(listModel.get(i)));
						scrollPane.invalidate(); // TODO: fix this.
					}
				}
			}
		});
		showCentered(this);
	}

	public <T> AlertFrame(final Map<String, T> map) {
        initCommon();
		processMap(map);
	}

	private <T> void processMap(final Map<String, T> map) {
		final DefaultListModel listModel = new DefaultListModel();

		int i = 0;
		for (Object value : new TreeSet<Object>(map.keySet())) {
			listModel.add(i++, value);
		}
		JList jList = new JList(listModel);
		jList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		jList.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent e) {
//				if (e.getValueIsAdjusting() == false || e.getFirstIndex() == -1) {
//					return;
//				}
				for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
					if (((JList) e.getSource()).isSelectedIndex(i)) {
						textArea.setText(String.valueOf(map.get(listModel.get(i))));
					}
				}
			}
		});
		jList.addMouseListener(new MouseAdapter() {
		    public void mouseClicked(MouseEvent evt) {
		        JList list = (JList)evt.getSource();
		        if (evt.getClickCount() == 2) {
		            int index = list.locationToIndex(evt.getPoint());
		            Object obj = map.get(listModel.get(index));
		            new AlertFrame(obj);
		        } 
		    }
		});		
		
		// jList.add
		getContentPane().add(new JScrollPane(jList), BorderLayout.NORTH);
		getContentPane().add(new JScrollPane(textArea), BorderLayout.CENTER);
		setSize(gd.getDisplayMode().getWidth() / 2, gd.getDisplayMode()
				.getWidth() / 2);
		showCentered(this);
	}
	public JTextArea getTextArea() {
		return textArea;
	}

	class DL implements DocumentListener {
		private AlertFrame parent;

		public DL(AlertFrame parent) {
			this.parent = parent;
		}

		public void removeUpdate(DocumentEvent event) {
			changed(event);
		}

		public void insertUpdate(DocumentEvent event) {
			changed(event);
		}

		public void changedUpdate(DocumentEvent event) {
			changed(event);
		}

		private void changed(DocumentEvent event) {
			if (!"".equals(parent.selectedRowIndex.getText())
					&& !"".equals(parent.selectedColIndex.getText())) {
				int intSelectedRowIndex = Integer
						.parseInt(parent.selectedRowIndex.getText());
				int intSelectedColIndex = Integer
						.parseInt(parent.selectedColIndex.getText());
				textArea.setText(String.valueOf(mapListTableModel.getValueAt(
						intSelectedRowIndex, intSelectedColIndex)));
			}
		}
	}
	public static void showCentered(JFrame frame) {
		GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
		final int x = (gd.getDisplayMode().getWidth() - frame.getWidth()) / 2;
		final int y = (gd.getDisplayMode().getHeight() - frame.getHeight()) / 2;
		frame.setLocation(x, y);
		frame.setVisible(true);
	}	

}

class SelectionDebugger implements ListSelectionListener {
	private ListSelectionModel listModel;
	private JTextField target;

	public SelectionDebugger(JTextField target, ListSelectionModel lsm) {
		this.listModel = lsm;
		this.target = target;
	}

	public void valueChanged(ListSelectionEvent lse) {
		if (!lse.getValueIsAdjusting()) {
			int[] selection = getSelectedIndices(
					listModel.getMinSelectionIndex(),
					listModel.getMaxSelectionIndex());
			if (selection.length == 0) {
			} else {
				for (int i = 0; i < selection.length; i++) {
					String text = String.valueOf(selection[i]);
					if (!"".equals(text.trim())) {
						target.setText(text);
					}
				}
			}

		}
	}

	protected int[] getSelectedIndices(int start, int stop) {
		if ((start == -1) || (stop == -1)) {
			// no selection, so return an empty array
			return new int[0];
		}
		int guesses[] = new int[stop - start + 1];
		int index = 0;
		// manually walk thru these
		for (int i = start; i <= stop; i++) {
			if (listModel.isSelectedIndex(i)) {
				guesses[index++] = i;
			}
		}
		int realthing[] = new int[index];
		System.arraycopy(guesses, 0, realthing, 0, index);
		return realthing;
	}
}

class MapListTableModel <T> extends AbstractTableModel {
	private static final long serialVersionUID = 1L;
	private List<Map<String, T>> mapList = new ArrayList<Map<String, T>>();
	private List<String> colNameList = new ArrayList<String>();

	public MapListTableModel(List<Map<String, T>> mapList,
			List<String> colNameList) {
		super();
		this.mapList = mapList;
		this.colNameList = colNameList;
	}

	public int getColumnCount() {
		return colNameList.size();
	}

	public String getColumnName(int columnIndex) {
		return colNameList.get(columnIndex);
	}

	public int getRowCount() {
		return mapList.size();
	}

	public Object getValueAt(int rowIndex, int columnIndex) {
		String columnName = getColumnName(columnIndex);
		Map<String, T> map = mapList.get(rowIndex);
		if (map == null)
			return null;
		return map.get(columnName);
	}
}

Sample Swing Test Application

vi src/main/java/AlertFrameTest.java

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class AlertFrameTest {
	public static void main(String args[]) throws Exception {
		testValue();
		testValueSet();
		testValueList();
		testValueMap();
		testValueListMap();		
	}

	/**
	 * Displays a single dialog with the value displayed. 
	 * 
	 * @throws Exception
	 */
	private static void testValue() throws Exception {
		new AlertFrame(123);
		new AlertFrame(123.5);
		new AlertFrame(new Integer(123));
		// Strings
		new AlertFrame("Testing Single String");
		// beans 
		// uses reflection to get a map of all the getters.
		// displays a jListbox with the getters.
		// clicking displays toString on bottom jTextArea.
		// double clicking opens new AlertFrame (value).
		new AlertFrame(new UserBean(1, "Bob", new UserBean(0, "Manager", null)));
	}

	/**
	 * Displays Dialog with a JListbox.
	 */
	private static void testValueSet() {
		// wrapped numbers 
		// clicking displays toString on bottom jTextArea.
		// double clicking does nothing.
		Set<Integer> intSet = new HashSet<Integer>();
		intSet.add(1);
		intSet.add(2);
		new AlertFrame(intSet);
		// Strings
		// clicking displays toString on bottom jTextArea.
		// double clicking does nothing.
		Set<String> set = new HashSet<String>();
		set.add("a");
		set.add("b");
		new AlertFrame(set);
		// beans 
		// clicking displays toString on bottom jTextArea.
		// double clicking opens new AlertFrame (value).
		Set<UserBean> userSet = new HashSet<UserBean>();
		userSet.add(new UserBean(1, "Bob", new UserBean(0, "Manager", null)));
		userSet.add(new UserBean(1, "Ted", new UserBean(0, "Manager", null)));
		new AlertFrame(userSet);
	}
	public static void testValueList() {	
		// Strings
		// clicking displays toString on bottom jTextArea.
		// double clicking does nothing.
		List<String> set = new ArrayList<String>();
		set.add("a");
		set.add("b");
		new AlertFrame(set);
		// beans 
		// clicking displays toString on bottom jTextArea.
		// double clicking opens new AlertFrame (value).
		List<UserBean> userSet = new ArrayList<UserBean>();
		userSet.add(new UserBean(1, "Bob", new UserBean(0, "Manager", null)));
		userSet.add(new UserBean(1, "Ted", new UserBean(0, "Manager", null)));
		new AlertFrame(userSet);
	}
	/**
	 * This is for properties. It displays a list box with the map keys with
	 * details displayed in a text area on the bottom.
	 */
	public static void testValueMap() {	
		// wrapped numbers (as map values)
		// clicking displays toString on bottom jTextArea.		
		// double clicking does nothing.
		Map <String, Integer> numericMap = new HashMap<String,Integer>();
		numericMap.put("a", 123);
		numericMap.put("b", 456);
		new AlertFrame(numericMap);
		// Strings (as map values)
		// clicking displays toString on bottom jTextArea.
		// double clicking does nothing.
		Map <String, String> stringMap = new HashMap<String,String>();
		stringMap.put("a", "string a");
		stringMap.put("b", "String b");
		new AlertFrame(stringMap);
		// beans (as map values)		
		// clicking displays toString on bottom jTextArea.
		// double clicking opens new AlertFrame (value).
		Map <String, UserBean> beanMap = new HashMap<String,UserBean>();
		beanMap.put("a", new UserBean(1, "Bob", new UserBean(0, "Manager", null)));
		beanMap.put("b", new UserBean(1, "Ted", new UserBean(0, "Manager", null)));
		new AlertFrame(beanMap);
	}
	/**
	 * This displays a table with columns that are based on map keys with 
	 * details displayed in the text area on the bottom.
	 */
	public static void testValueListMap() {	
		{
			// wrapped numbers (as table cell values)
			// clicking displays toString on bottom jTextArea.
			// double clicking does nothing.
			List<Map<String,Integer>> mapList = new ArrayList<Map<String,Integer>>();
			mapList.add(Collections.singletonMap("key", 123));
			mapList.add(Collections.singletonMap("key", 456));
			new AlertFrame(mapList, Arrays.asList(new String[] {"key"}));
		}{
			// Strings (as table cell values)
			// clicking displays toString on bottom jTextArea.
			// double clicking does nothing.
			List<Map<String,String>> mapList = new ArrayList<Map<String,String>>();
			mapList.add(Collections.singletonMap("key", "String-123"));
			mapList.add(Collections.singletonMap("key", "String-456"));			
			new AlertFrame(mapList, Arrays.asList(new String[] {"key"}));
		}{
			// beans (have beans as table cells)		
			// clicking displays toString on bottom jTextArea.
			// double clicking opens new AlertFrame (value).
			List<Map<String,Object>> mapList = new ArrayList<Map<String,Object>>();
			Map<String,Object> hmap = new HashMap<String,Object>();
			hmap.put("key", new UserBean(1, "Bob", new UserBean(0, "Manager", null)));
			hmap.put("key2", "test");
			hmap.put("key3", 123);
			mapList.add(hmap);
			hmap = new HashMap<String,Object>();
			hmap.put("key", new UserBean(1, "Ted", new UserBean(0, "Manager", null)));
			hmap.put("key2", "test");
			hmap.put("key3", 123);
			mapList.add(hmap);
			new AlertFrame(mapList, new ArrayList<String>(hmap.keySet()));
		}
	}
}

To run the app just type the following:

mvn exec:java -Dexec.mainClass=AlertFrame

Finally the pom.xml

vi pom.xml

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>alertFrame</artifactId>
  <version>0.0.1-SNAPSHOT</version>
    <pluginRepositories>
      <pluginRepository>
        <id>numberformat-releases</id>
        <url>https://raw.github.com/numberformat/20130213/master/repo</url>
      </pluginRepository>
    </pluginRepositories>  
  
  <dependencies>
  	<dependency>
  		<groupId>junit</groupId>
  		<artifactId>junit</artifactId>
  		<version>4.11</version>
  	</dependency>
  	<dependency>
  		<groupId>commons-beanutils</groupId>
  		<artifactId>commons-beanutils</artifactId>
  		<version>1.8.3</version>
  	</dependency>
  </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>github.numberformat</groupId>
                <artifactId>blog-plugin</artifactId>
                <version>1.0-SNAPSHOT</version>
                <configuration>
                <gitUrl>https://github.com/numberformat/wordpress/tree/master/20131013/${artifactId}</gitUrl>
                </configuration>
            <executions>
              <execution>
                <id>1</id>
                <phase>site</phase>
                <goals>
                  <goal>generate</goal>
                </goals>
              </execution>
            </executions>
            </plugin>
        </plugins>
    </build>  
</project>
Full downloadable source for this page is available here.



Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 77 other followers

February 2017
S M T W T F S
« Mar    
 1234
567891011
12131415161718
19202122232425
262728  

Blog Stats

  • 795,599 hits