You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@batchee.apache.org by st...@apache.org on 2015/11/09 21:34:47 UTC

incubator-batchee git commit: BATCHEE-66 add support for joda, java8 time and enums

Repository: incubator-batchee
Updated Branches:
  refs/heads/master 1a0259c6f -> 8d89ac8cd


BATCHEE-66 add support for joda, java8 time and enums


Project: http://git-wip-us.apache.org/repos/asf/incubator-batchee/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-batchee/commit/8d89ac8c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-batchee/tree/8d89ac8c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-batchee/diff/8d89ac8c

Branch: refs/heads/master
Commit: 8d89ac8cde1730d249290ea0e3697159e77b0f60
Parents: 1a0259c
Author: Mark Struberg <st...@apache.org>
Authored: Mon Nov 9 21:17:03 2015 +0100
Committer: Mark Struberg <st...@apache.org>
Committed: Mon Nov 9 21:17:03 2015 +0100

----------------------------------------------------------------------
 jbatch/pom.xml                                  |   4 +
 .../data/DefaultDataRepresentationService.java  | 161 +++++++++++++++++--
 .../DefaultDataRepresentationServiceTest.java   |  40 ++++-
 pom.xml                                         |   9 ++
 4 files changed, 197 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/8d89ac8c/jbatch/pom.xml
----------------------------------------------------------------------
diff --git a/jbatch/pom.xml b/jbatch/pom.xml
index 8b0eac2..a746164 100644
--- a/jbatch/pom.xml
+++ b/jbatch/pom.xml
@@ -85,6 +85,10 @@
       <artifactId>derby</artifactId>
     </dependency>
     <dependency>
+      <groupId>joda-time</groupId>
+      <artifactId>joda-time</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.openejb</groupId>
       <artifactId>openejb-core</artifactId>
       <version>4.7.2</version>

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/8d89ac8c/jbatch/src/main/java/org/apache/batchee/container/services/data/DefaultDataRepresentationService.java
----------------------------------------------------------------------
diff --git a/jbatch/src/main/java/org/apache/batchee/container/services/data/DefaultDataRepresentationService.java b/jbatch/src/main/java/org/apache/batchee/container/services/data/DefaultDataRepresentationService.java
index 6056ded..7453059 100644
--- a/jbatch/src/main/java/org/apache/batchee/container/services/data/DefaultDataRepresentationService.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/services/data/DefaultDataRepresentationService.java
@@ -20,6 +20,7 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.ObjectOutputStream;
+import java.lang.reflect.Method;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.sql.Timestamp;
@@ -44,13 +45,14 @@ public class DefaultDataRepresentationService implements DataRepresentationServi
 
 
     public static final String BATCHEE_DATA_PREFIX = "BatchEE_data" + BATCHEE_SPLIT_TOKEN;
-    private Charset UTF8_CHARSET = StandardCharsets.UTF_8;
+
+    private static final Charset UTF8_CHARSET = StandardCharsets.UTF_8;
 
     private static final Logger LOGGER = Logger.getLogger(DefaultDataRepresentationService.class.getName());
 
+
     @Override
     public void init(Properties batchConfig) {
-        // nothing to do
     }
 
     @Override
@@ -64,6 +66,18 @@ public class DefaultDataRepresentationService implements DataRepresentationServi
             serialValue = convertJava7DateTypes(dataObject);
         }
         if (serialValue == null) {
+            serialValue = convertJava8DateTypes(dataObject);
+        }
+        if (serialValue == null) {
+            serialValue = convertJodaDateTypes(dataObject);
+        }
+        if (serialValue == null) {
+            serialValue = convertCustomEnumTypes(dataObject);
+        }
+        if (serialValue == null) {
+            serialValue = convertCustomTypes(dataObject);
+        }
+        if (serialValue == null) {
             // as last resort we do a simple java serialisation
             serialValue = convertSerializableObjectTypes(dataObject);
         }
@@ -89,6 +103,18 @@ public class DefaultDataRepresentationService implements DataRepresentationServi
             if (data == null) {
                 data = convertBackJava7DateTypes(typeVal, valueVal);
             }
+            if (data == null) {
+                data = convertBackJava8DateTypes(typeVal, valueVal);
+            }
+            if (data == null) {
+                data = convertBackJodaDateTypes(typeVal, valueVal);
+            }
+            if (data == null) {
+                data = convertBackCustomEnumTypes(typeVal, valueVal);
+            }
+            if (data == null) {
+                data = convertBackCustomTypes(typeVal, valueVal);
+            }
         }
 
 
@@ -106,12 +132,45 @@ public class DefaultDataRepresentationService implements DataRepresentationServi
         return data;
     }
 
+    private <T> byte[] convertCustomEnumTypes(T dataObject) {
+        if (dataObject instanceof Enum) {
+            return toBatchEeData(dataObject.getClass(), ((Enum) dataObject).name());
+        }
+        return null;
+    }
+
+    private <T> T convertBackCustomEnumTypes(String typeVal, String valueVal) {
+        try {
+            Class typeClass = getClassLoader().loadClass(typeVal);
+            if (typeClass.isEnum()) {
+                return (T) Enum.valueOf(typeClass, valueVal);
+            }
+        } catch (ClassNotFoundException e) {
+            throw new IllegalStateException("Cannot convert back BatchEE data: " + valueVal + " of Enum type " + typeVal);
+        }
+        return null;
+    }
+
+    /**
+     * This is an extension point for other serialisation algorithms.
+     */
+    protected <T> byte[] convertCustomTypes(T dataObject) {
+        return null;
+    }
+
+    /**
+     * This is an extension point for other serialsation algorithms.
+     */
+    private <T> T convertBackCustomTypes(String typeVal, String valueVal) {
+        return null;
+    }
+
     /**
      * This method converts java native types to a 'nice' string representation
      * which will be stored in the database.
      * @return the String representation or {@code null} if the dataObject was not a native Java type
      */
-    private <T> byte[] convertJavaNativeTypes(T dataObject) {
+    protected <T> byte[] convertJavaNativeTypes(T dataObject) {
         // convert int, Integer, etc
         if (dataObject instanceof Integer ||
             dataObject instanceof String ||
@@ -125,7 +184,7 @@ public class DefaultDataRepresentationService implements DataRepresentationServi
         return null;
     }
 
-    private <T> T convertBackJavaNativeTypes(String typeVal, String value) {
+    protected <T> T convertBackJavaNativeTypes(String typeVal, String value) {
         if (Integer.class.getName().equals(typeVal)) {
             return (T) Integer.valueOf(value);
         }
@@ -156,7 +215,7 @@ public class DefaultDataRepresentationService implements DataRepresentationServi
      *     <li>java.sql.Timestamp</li>
      * </ul>
      */
-    private <T> byte[] convertJava7DateTypes(T dataObject) {
+    protected <T> byte[] convertJava7DateTypes(T dataObject) {
         if (dataObject.getClass().equals(Date.class)) {
             return toBatchEeData(Date.class, getSimpleDateFormat().format((Date) dataObject));
         }
@@ -170,7 +229,7 @@ public class DefaultDataRepresentationService implements DataRepresentationServi
         return null;
     }
 
-    private <T> T convertBackJava7DateTypes(String typeVal, String valueVal) {
+    protected <T> T convertBackJava7DateTypes(String typeVal, String valueVal) {
         if (Date.class.getName().equals(typeVal)) {
             try {
                 return (T) getSimpleDateFormat().parse(valueVal);
@@ -194,49 +253,123 @@ public class DefaultDataRepresentationService implements DataRepresentationServi
         return null;
     }
 
+    private <T> byte[] convertJodaDateTypes(T dataObject) {
+        String className = dataObject.getClass().getName();
+        if (className.equals("org.joda.time.LocalDate") ||
+            className.equals("org.joda.time.LocalDateTime") ||
+            className.equals("org.joda.time.LocalTime")) {
+            // joda date API does the right thing on toString
+            return toBatchEeData(dataObject.getClass(), dataObject.toString());
+        }
+        return null;
+    }
 
+    private <T> T convertBackJodaDateTypes(String typeVal, String valueVal) {
+        if (typeVal.equals("org.joda.time.LocalDate") ||
+            typeVal.equals("org.joda.time.LocalDateTime") ||
+            typeVal.equals("org.joda.time.LocalTime")) {
+            return (T) invokeStaticMethod(typeVal, "parse", String.class, valueVal);
+        }
+        return null;
+    }
+
+    private <T> byte[] convertJava8DateTypes(T dataObject) {
+        String className = dataObject.getClass().getName();
+        if (className.equals("java.time.LocalDate") ||
+            className.equals("java.time.LocalDateTime") ||
+            className.equals("java.time.LocalTime")) {
+            // joda date API does the right thing on toString
+            return toBatchEeData(dataObject.getClass(), dataObject.toString());
+        }
+        return null;
+    }
+
+    private <T> T convertBackJava8DateTypes(String typeVal, String valueVal) {
+        if (typeVal.equals("java.time.LocalDate") ||
+            typeVal.equals("java.time.LocalDateTime") ||
+            typeVal.equals("java.time.LocalTime")) {
+            return (T) invokeStaticMethod(typeVal, "parse", CharSequence.class, valueVal);
+        }
+        return null;
+    }
+
+    protected Object invokeStaticMethod(String typeVal, String methodName, Class paramType, String valueVal) {
+        try {
+            Class<?> typeClass = getClassLoader().loadClass(typeVal);
+            Method method = typeClass.getMethod(methodName, paramType);
+            return method.invoke(null, valueVal);
+        } catch (ReflectiveOperationException e) {
+            throw new BatchContainerServiceException("Cannot convert data [" + valueVal + "] of type [" + typeVal + "]", e );
+        }
+    }
 
     /**
      * This is the default operation if no other way to serialise the data was used
      */
-    private <T> byte[] convertSerializableObjectTypes(T dataObject) {
+    protected <T> byte[] convertSerializableObjectTypes(T dataObject) {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         ObjectOutputStream oos = null;
         try {
             oos = new ObjectOutputStream(baos);
             oos.writeObject(dataObject);
-            oos.close();
         } catch (IOException e) {
             throw new BatchContainerServiceException("Cannot convert data for [" + dataObject.toString() + "]");
+        } finally {
+            if (oos != null) {
+                try {
+                    oos.close();
+                } catch (IOException e) {
+                    // meh give up...
+                }
+            }
         }
         return baos.toByteArray();
     }
 
-    private <T> T convertBackSerializableObjectTypes(byte[] internalRepresentation) {
+
+    protected <T> T convertBackSerializableObjectTypes(byte[] internalRepresentation) {
         final ByteArrayInputStream readerChkptBA = new ByteArrayInputStream(internalRepresentation);
-        TCCLObjectInputStream readerOIS;
+        TCCLObjectInputStream readerOIS = null;
         try {
             // need to use the TCCL in case the batch is in a webapp but jbatch runtime is provided in a parent ClassLoader
             readerOIS = new TCCLObjectInputStream(readerChkptBA);
             T instance = (T) readerOIS.readObject();
-            readerOIS.close();
             return instance;
         } catch (final Exception ex) {
             return null;
+        } finally {
+            if (readerOIS != null) {
+                try {
+                    readerOIS.close();
+                } catch (IOException e) {
+                    // meh give up...
+                }
+            }
         }
     }
 
-    private byte[] toBatchEeData(Class<?> type, String stringRepresentation) {
+    protected byte[] toBatchEeData(Class<?> type, String stringRepresentation) {
         return (BATCHEE_DATA_PREFIX + type.getName() + BATCHEE_SPLIT_TOKEN + stringRepresentation).getBytes(UTF8_CHARSET);
     }
 
-    private SimpleDateFormat getSimpleDateFormat() {
+    protected ClassLoader getClassLoader() {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        if (cl == null) {
+            cl = this.getClass().getClassLoader();
+        }
+        return cl;
+    }
+
+    protected SimpleDateFormat getSimpleDateFormat() {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
         sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
         return sdf;
     }
 
-    private SimpleDateFormat getTimestampDateFormat() {
+    /**
+     * Attention: The nanos must get concatenated separately as there is no formatter for nanos!
+     */
+    protected SimpleDateFormat getTimestampDateFormat() {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
         sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
         return sdf;

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/8d89ac8c/jbatch/src/test/java/org/apache/batchee/test/data/DefaultDataRepresentationServiceTest.java
----------------------------------------------------------------------
diff --git a/jbatch/src/test/java/org/apache/batchee/test/data/DefaultDataRepresentationServiceTest.java b/jbatch/src/test/java/org/apache/batchee/test/data/DefaultDataRepresentationServiceTest.java
index a60f554..e5fba15 100644
--- a/jbatch/src/test/java/org/apache/batchee/test/data/DefaultDataRepresentationServiceTest.java
+++ b/jbatch/src/test/java/org/apache/batchee/test/data/DefaultDataRepresentationServiceTest.java
@@ -17,6 +17,7 @@
 package org.apache.batchee.test.data;
 
 import java.io.Serializable;
+import java.lang.reflect.Method;
 import java.sql.Timestamp;
 import java.util.Date;
 import java.util.List;
@@ -32,6 +33,9 @@ import javax.batch.runtime.StepExecution;
 import org.apache.batchee.container.services.data.DefaultDataRepresentationService;
 import org.apache.batchee.spi.DataRepresentationService;
 import org.apache.batchee.util.Batches;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.joda.time.LocalTime;
 import org.junit.Assert;
 import org.junit.Test;
 import static org.testng.Assert.assertEquals;
@@ -72,7 +76,15 @@ public class DefaultDataRepresentationServiceTest {
 
     @Test
     public void testJavaCustomEnum() {
-        //X TODO
+        assertRoundTripEquals(MySampleEnum.VALUE1, true);
+        assertRoundTripEquals(MySampleEnum.VALUE2, true);
+        assertRoundTripEquals(MySampleEnum.ANOTHER_VALUE, true);
+    }
+
+    public enum MySampleEnum {
+        VALUE1,
+        VALUE2,
+        ANOTHER_VALUE
     }
 
     @Test
@@ -102,12 +114,32 @@ public class DefaultDataRepresentationServiceTest {
 
     @Test
     public void testJava8DateTimeViaReflection() {
-        //X TODO
+        Object java8LocalDate = createJava8datetype("java.time.LocalDate");
+        Object java8LocalTime = createJava8datetype("java.time.LocalTime");
+        Object java8LocalDateTime = createJava8datetype("java.time.LocalDateTime");
+        if (java8LocalDate != null) {
+            assertRoundTripEquals(java8LocalDate, true);
+            assertRoundTripEquals(java8LocalTime, true);
+            assertRoundTripEquals(java8LocalDateTime, true);
+        }
+    }
+
+    private Object createJava8datetype(String className) {
+        try {
+            Class<?> clazz = Class.forName(className);
+            Method now = clazz.getMethod("now");
+            return now.invoke(null);
+        } catch (ReflectiveOperationException e) {
+            // all fine, we are just not running on java8
+        }
+        return null;
     }
 
     @Test
     public void testJodaDateTimeViaReflection() {
-        //X TODO
+        assertRoundTripEquals(LocalDate.now(), true);
+        assertRoundTripEquals(LocalDateTime.now(), true);
+        assertRoundTripEquals(LocalTime.now(), true);
     }
 
     @Test
@@ -190,6 +222,7 @@ public class DefaultDataRepresentationServiceTest {
     }
 
 
+    @SuppressWarnings("unused")
     public static class DummyReaderWithCheckpoint extends AbstractItemReader {
 
 
@@ -213,6 +246,7 @@ public class DefaultDataRepresentationServiceTest {
         }
     }
 
+    @SuppressWarnings("unused")
     public static class DummyWriterWithCheckpoint extends AbstractItemWriter {
 
         private Integer lastCount = null;

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/8d89ac8c/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index be788b6..0015acf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,7 @@
         <atinject.version>1.0</atinject.version>
         <batch-api.version>1.0</batch-api.version>
         <jackson.version>2.2.2</jackson.version>
+        <jodatime.version>2.8.1</jodatime.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <batchee.scmPubUrl>https://svn.apache.org/repos/asf/incubator/batchee/site</batchee.scmPubUrl>
         <batchee.scmPubCheckoutDirectory>${basedir}/.site-content</batchee.scmPubCheckoutDirectory>
@@ -158,6 +159,14 @@
                 <scope>test</scope>
             </dependency>
 
+            <dependency>
+                <!-- We have optional support for joda time IF it is available on the classpath -->
+                <groupId>joda-time</groupId>
+                <artifactId>joda-time</artifactId>
+                <version>${jodatime.version}</version>
+                <scope>test</scope>
+            </dependency>
+
             <!-- Testing libraries -->
             <dependency> <!-- TCK and default tests -->
                 <groupId>org.testng</groupId>