BATCH-1684 - Allow serializer to be injected into JobRepository

* Updated the job-repository tag to allow for a serializer and deserializer to be injected
* Updated the XStreamExecutionContextStringSerializer to implement Serializer and Deserializer
* Replaced the ExecutionContextStringSerializer with the ExecutionContextSerializer interface
* Updated the XSD and parser to accept the injection of an implementation of the ExecutionContextSerializer from the job-repository tag
* Updated the JdbcExecutionContextDao to use injected ExecutionContextSerializer implementation

Reference: https://jira.springsource.org/browse/BATCH-1684
This commit is contained in:
Michael Minella
2012-10-03 18:14:22 -05:00
committed by Gunnar Hillert
parent 38ef37b882
commit 64ca2608ad
15 changed files with 747 additions and 341 deletions

View File

@@ -0,0 +1,169 @@
/**
*
*/
package org.springframework.batch.core.repository.dao;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
/**
* @author Michael Minella
*
*/
public class DefaultExecutionContextSerializerTests {
private DefaultExecutionContextSerializer serializer;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
serializer = new DefaultExecutionContextSerializer();
}
@Test
public void testSerializeAMap() throws Exception {
Map<String, Object> m1 = new HashMap<String, Object>();
m1.put("object1", Long.valueOf(12345L));
m1.put("object2", "OBJECT TWO");
// Use a date after 1971 (otherwise daylight saving screws up)...
m1.put("object3", new Date(123456790123L));
m1.put("object4", new Double(1234567.1234D));
Map<String, Object> m2 = serializationRoundTrip(m1);
compareContexts(m1, m2);
}
@Test
public void testComplexObject() throws Exception {
Map<String, Object> m1 = new HashMap<String, Object>();
ComplexObject o1 = new ComplexObject();
o1.setName("02345");
Map<String, Object> m = new HashMap<String, Object>();
m.put("object1", Long.valueOf(12345L));
m.put("object2", "OBJECT TWO");
o1.setMap(m);
o1.setNumber(new BigDecimal("12345.67"));
ComplexObject o2 = new ComplexObject();
o2.setName("Inner Object");
o2.setMap(m);
o2.setNumber(new BigDecimal("98765.43"));
o1.setObj(o2);
m1.put("co", o1);
Map<String, Object> m2 = serializationRoundTrip(m1);
compareContexts(m1, m2);
}
@Test (expected=IllegalArgumentException.class)
public void testNullSerialization() throws Exception {
serializer.serialize(null, null);
}
private void compareContexts(Map<String, Object> m1, Map<String, Object> m2) {
for (String key : m1.keySet()) {
System.out.println("m1 = " + m1 + " m2 = " + m2);
assertEquals("Bad key/value for " + key, m1.get(key), m2.get(key));
}
}
@SuppressWarnings("unchecked")
private Map<String, Object> serializationRoundTrip(Map<String, Object> m1) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
serializer.serialize(m1, out);
String s = out.toString();
InputStream in = new ByteArrayInputStream(s.getBytes());
Map<String, Object> m2 = (Map<String, Object>) serializer.deserialize(in);
return m2;
}
@SuppressWarnings("unused")
private static class ComplexObject implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private BigDecimal number;
private ComplexObject obj;
private Map<String,Object> map;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getNumber() {
return number;
}
public void setNumber(BigDecimal number) {
this.number = number;
}
public ComplexObject getObj() {
return obj;
}
public void setObj(ComplexObject obj) {
this.obj = obj;
}
public Map<String,Object> getMap() {
return map;
}
public void setMap(Map<String,Object> map) {
this.map = map;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ComplexObject that = (ComplexObject) o;
if (map != null ? !map.equals(that.map) : that.map != null) return false;
if (name != null ? !name.equals(that.name) : that.name != null) return false;
if (number != null ? !number.equals(that.number) : that.number != null) return false;
if (obj != null ? !obj.equals(that.obj) : that.obj != null) return false;
return true;
}
@Override
public int hashCode() {
int result;
result = (name != null ? name.hashCode() : 0);
result = 31 * result + (number != null ? number.hashCode() : 0);
result = 31 * result + (obj != null ? obj.hashCode() : 0);
result = 31 * result + (map != null ? map.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ComplexObject [name=" + name + ", number=" + number + "]";
}
}
}

View File

@@ -2,6 +2,9 @@ package org.springframework.batch.core.repository.dao;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
@@ -9,22 +12,26 @@ import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.repository.ExecutionContextSerializer;
/**
* @author Thomas Risberg
* @author Michael Minella
*/
public class XStreamExecutionContextStringSerializerTests {
ExecutionContextStringSerializer serializer;
ExecutionContextSerializer serializer;
@Before
public void onSetUp() throws Exception {
serializer = new XStreamExecutionContextStringSerializer();
((XStreamExecutionContextStringSerializer)serializer).afterPropertiesSet();
XStreamExecutionContextStringSerializer serializerDeserializer = new XStreamExecutionContextStringSerializer();
(serializerDeserializer).afterPropertiesSet();
serializer = serializerDeserializer;
}
@Test
public void testSerializeAMap() {
public void testSerializeAMap() throws Exception {
Map<String, Object> m1 = new HashMap<String, Object>();
m1.put("object1", Long.valueOf(12345L));
m1.put("object2", "OBJECT TWO");
@@ -32,15 +39,13 @@ public class XStreamExecutionContextStringSerializerTests {
m1.put("object3", new Date(123456790123L));
m1.put("object4", new Double(1234567.1234D));
String s = serializer.serialize(m1);
Map<String, Object> m2 = serializer.deserialize(s);
Map<String, Object> m2 = serializationRoundTrip(m1);
compareContexts(m1, m2);
}
@Test
public void testComplexObject() {
public void testComplexObject() throws Exception {
Map<String, Object> m1 = new HashMap<String, Object>();
ComplexObject o1 = new ComplexObject();
o1.setName("02345");
@@ -56,19 +61,36 @@ public class XStreamExecutionContextStringSerializerTests {
o1.setObj(o2);
m1.put("co", o1);
String s = serializer.serialize(m1);
Map<String, Object> m2 = serializer.deserialize(s);
Map<String, Object> m2 = serializationRoundTrip(m1);
compareContexts(m1, m2);
}
@Test (expected=IllegalArgumentException.class)
@SuppressWarnings("unchecked")
public void testNullSerialization() throws Exception {
serializer.serialize(null, null);
}
private void compareContexts(Map<String, Object> m1, Map<String, Object> m2) {
for (String key : m1.keySet()) {
System.out.println("m1 = " + m1 + " m2 = " + m2);
assertEquals("Bad key/value for " + key, m1.get(key), m2.get(key));
}
}
@SuppressWarnings("unchecked")
private Map<String, Object> serializationRoundTrip(Map<String, Object> m1) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
serializer.serialize(m1, out);
String s = out.toString();
ByteArrayInputStream in = new ByteArrayInputStream(s.getBytes());
Map<String, Object> m2 = (Map<String, Object>) serializer.deserialize(in);
return m2;
}
@SuppressWarnings("unused")
private static class ComplexObject {
private String name;
@@ -109,6 +131,7 @@ public class XStreamExecutionContextStringSerializerTests {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@@ -123,6 +146,7 @@ public class XStreamExecutionContextStringSerializerTests {
return true;
}
@Override
public int hashCode() {
int result;
result = (name != null ? name.hashCode() : 0);
@@ -136,6 +160,6 @@ public class XStreamExecutionContextStringSerializerTests {
public String toString() {
return "ComplexObject [name=" + name + ", number=" + number + "]";
}
}
}

View File

@@ -26,14 +26,19 @@ import static org.easymock.EasyMock.verify;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.repository.ExecutionContextSerializer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer;
import org.springframework.batch.core.repository.dao.XStreamExecutionContextStringSerializer;
import org.springframework.batch.item.database.support.DataFieldMaxValueIncrementerFactory;
import org.springframework.core.serializer.Serializer;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
@@ -45,7 +50,7 @@ import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
* @author Lucas Ward
*
*
*/
public class JobRepositoryFactoryBeanTests {
@@ -81,26 +86,26 @@ public class JobRepositoryFactoryBeanTests {
expect(dataSource.getConnection()).andReturn(con);
expect(con.getMetaData()).andReturn(dmd);
expect(dmd.getDatabaseProductName()).andReturn("Oracle");
expect(incrementerFactory.isSupportedIncrementerType("ORACLE")).andReturn(true);
expect(incrementerFactory.getSupportedIncrementerTypes()).andReturn(new String[0]);
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_SEQ")).andReturn(new StubIncrementer());
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_EXECUTION_SEQ")).andReturn(new StubIncrementer());
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "STEP_EXECUTION_SEQ")).andReturn(new StubIncrementer());
replay(dataSource,con,dmd, incrementerFactory);
factory.afterPropertiesSet();
factory.getObject();
verify(incrementerFactory);
}
@Test
public void testOracleLobHandler() throws Exception {
factory.setDatabaseType("ORACLE");
incrementerFactory = createNiceMock(DataFieldMaxValueIncrementerFactory.class);
expect(incrementerFactory.isSupportedIncrementerType("ORACLE")).andReturn(true);
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_SEQ")).andReturn(new StubIncrementer());
@@ -112,14 +117,14 @@ public class JobRepositoryFactoryBeanTests {
factory.afterPropertiesSet();
LobHandler lobHandler = (LobHandler) ReflectionTestUtils.getField(factory, "lobHandler");
assertTrue(lobHandler instanceof OracleLobHandler);
}
@Test
public void testCustomLobHandler() throws Exception {
factory.setDatabaseType("ORACLE");
incrementerFactory = createNiceMock(DataFieldMaxValueIncrementerFactory.class);
expect(incrementerFactory.isSupportedIncrementerType("ORACLE")).andReturn(true);
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_SEQ")).andReturn(new StubIncrementer());
@@ -127,13 +132,52 @@ public class JobRepositoryFactoryBeanTests {
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "STEP_EXECUTION_SEQ")).andReturn(new StubIncrementer());
replay(dataSource,incrementerFactory);
factory.setIncrementerFactory(incrementerFactory);
LobHandler lobHandler = new DefaultLobHandler();
factory.setLobHandler(lobHandler);
factory.afterPropertiesSet();
assertEquals(lobHandler, ReflectionTestUtils.getField(factory, "lobHandler"));
}
@Test
@SuppressWarnings("unchecked")
public void tesDefaultSerializer() throws Exception {
factory.setDatabaseType("ORACLE");
incrementerFactory = createNiceMock(DataFieldMaxValueIncrementerFactory.class);
expect(incrementerFactory.isSupportedIncrementerType("ORACLE")).andReturn(true);
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_SEQ")).andReturn(new StubIncrementer());
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_EXECUTION_SEQ")).andReturn(new StubIncrementer());
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "STEP_EXECUTION_SEQ")).andReturn(new StubIncrementer());
replay(dataSource,incrementerFactory);
factory.setIncrementerFactory(incrementerFactory);
factory.afterPropertiesSet();
Serializer<Map<String, Object>> serializer = (Serializer<Map<String,Object>>) ReflectionTestUtils.getField(factory, "serializer");
assertTrue(serializer instanceof XStreamExecutionContextStringSerializer);
}
@Test
public void testCustomSerializer() throws Exception {
factory.setDatabaseType("ORACLE");
incrementerFactory = createNiceMock(DataFieldMaxValueIncrementerFactory.class);
expect(incrementerFactory.isSupportedIncrementerType("ORACLE")).andReturn(true);
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_SEQ")).andReturn(new StubIncrementer());
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_EXECUTION_SEQ")).andReturn(new StubIncrementer());
expect(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "STEP_EXECUTION_SEQ")).andReturn(new StubIncrementer());
replay(dataSource,incrementerFactory);
factory.setIncrementerFactory(incrementerFactory);
ExecutionContextSerializer customSerializer = new DefaultExecutionContextSerializer();
factory.setSerializer(customSerializer);
factory.afterPropertiesSet();
assertEquals(customSerializer, ReflectionTestUtils.getField(factory, "serializer"));
}
@Test
@@ -161,7 +205,7 @@ public class JobRepositoryFactoryBeanTests {
expect(incrementerFactory.isSupportedIncrementerType("mockDb")).andReturn(true);
expect(incrementerFactory.getSupportedIncrementerTypes()).andReturn(new String[0]);
replay(incrementerFactory);
factory.afterPropertiesSet();
fail();
}