aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Moger <james.moger@gitblit.com>2014-11-07 09:28:57 -0500
committerJames Moger <james.moger@gitblit.com>2014-11-09 11:15:14 -0500
commitdb0d58c22a0bd4fa2baf023428599757aa4db381 (patch)
tree9199c08f6b4c4eb217cf6ebb732d8ce5d9856a46
parent468775706732f3e85af878710aaf95c81f5b60f2 (diff)
downloadiciql-db0d58c22a0bd4fa2baf023428599757aa4db381.tar.gz
iciql-db0d58c22a0bd4fa2baf023428599757aa4db381.zip
Revise type adapter definition to be a separate annotation
-rw-r--r--build.xml1
-rw-r--r--src/main/java/com/iciql/Iciql.java46
-rw-r--r--src/main/java/com/iciql/SQLDialectPostgreSQL.java33
-rw-r--r--src/main/java/com/iciql/TableDefinition.java18
-rw-r--r--src/main/java/com/iciql/util/Utils.java23
-rw-r--r--src/site/dta.mkd104
-rw-r--r--src/test/java/com/iciql/test/DataTypeAdapterTest.java21
7 files changed, 199 insertions, 47 deletions
diff --git a/build.xml b/build.xml
index d53c820..bcbcd7f 100644
--- a/build.xml
+++ b/build.xml
@@ -138,6 +138,7 @@
<menu name="getting started" pager="true" pagerPlacement="bottom" pagerLayout="justified">
<page name="models" src="model_classes.mkd" out="model_classes.html" headerLinks="true" />
<page name="versioning" src="table_versioning.mkd" out="table_versioning.html" headerLinks="true" />
+ <page name="data type adapters" src="dta.mkd" out="dta.html" headerLinks="true" />
<page name="usage" src="usage.mkd" out="usage.html" headerLinks="true" />
<page name="examples" src="examples.mkd" out="examples.html" headerLinks="true" />
<page name="tools" src="tools.mkd" out="tools.html" headerLinks="true" />
diff --git a/src/main/java/com/iciql/Iciql.java b/src/main/java/com/iciql/Iciql.java
index 05cceeb..524faa8 100644
--- a/src/main/java/com/iciql/Iciql.java
+++ b/src/main/java/com/iciql/Iciql.java
@@ -657,14 +657,6 @@ public interface Iciql {
*/
String defaultValue() default "";
- /**
- * Allows specifying a custom data type adapter.
- * <p>
- * For example, you might use this option to map a Postgres JSON column.
- * </p>
- */
- Class<? extends DataTypeAdapter<?>> typeAdapter() default StandardJDBCTypeAdapter.class;
-
}
/**
@@ -741,6 +733,17 @@ public interface Iciql {
void defineIQ();
/**
+ * Specify a custom type adapter for a method return type, a class field, or a method
+ * parameter. Type adapters allow you to transform content received from or inserted into
+ * a database field.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
+ public @interface TypeAdapter {
+ Class<? extends DataTypeAdapter<?>> value();
+ }
+
+ /**
* Interface to allow implementations of custom data type adapters for supporting
* database-specific data types, like the Postgres 'json' or 'xml' types,
* or for supporting other object serialization schemes.
@@ -782,31 +785,4 @@ public interface Iciql {
}
- /**
- * The standard type adapter allows iciql to use it's internal utility convert functions
- * to handle the standard JDBC types.
- */
- public final static class StandardJDBCTypeAdapter implements DataTypeAdapter<Object> {
-
- @Override
- public String getDataType() {
- throw new RuntimeException("This adapter is for all standard JDBC types.");
- }
-
- @Override
- public Class<Object> getJavaType() {
- throw new RuntimeException("This adapter is for all standard JDBC types.");
- }
-
- @Override
- public Object serialize(Object value) {
- return value;
- }
-
- @Override
- public Object deserialize(Object value) {
- return value;
- }
-
- }
}
diff --git a/src/main/java/com/iciql/SQLDialectPostgreSQL.java b/src/main/java/com/iciql/SQLDialectPostgreSQL.java
index b5ac5c3..f10017c 100644
--- a/src/main/java/com/iciql/SQLDialectPostgreSQL.java
+++ b/src/main/java/com/iciql/SQLDialectPostgreSQL.java
@@ -140,6 +140,39 @@ public class SQLDialectPostgreSQL extends SQLDialectDefault {
}
/**
+ * Handles transforming raw strings to/from the Postgres JSONB data type.
+ */
+ public class JsonbStringAdapter implements DataTypeAdapter<String> {
+
+ @Override
+ public String getDataType() {
+ return "jsonb";
+ }
+
+ @Override
+ public Class<String> getJavaType() {
+ return String.class;
+ }
+
+ @Override
+ public Object serialize(String value) {
+ PGobject pg = new PGobject();
+ pg.setType(getDataType());
+ try {
+ pg.setValue(value);
+ } catch (SQLException e) {
+ // not thrown on base PGobject
+ }
+ return pg;
+ }
+
+ @Override
+ public String deserialize(Object value) {
+ return value.toString();
+ }
+ }
+
+ /**
* Handles transforming raw strings to/from the Postgres XML data type.
*/
public class XmlStringAdapter implements DataTypeAdapter<String> {
diff --git a/src/main/java/com/iciql/TableDefinition.java b/src/main/java/com/iciql/TableDefinition.java
index fc83d29..e4fff87 100644
--- a/src/main/java/com/iciql/TableDefinition.java
+++ b/src/main/java/com/iciql/TableDefinition.java
@@ -51,7 +51,6 @@ import com.iciql.Iciql.IQTable;
import com.iciql.Iciql.IQVersion;
import com.iciql.Iciql.IQView;
import com.iciql.Iciql.IndexType;
-import com.iciql.Iciql.StandardJDBCTypeAdapter;
import com.iciql.util.IciqlLogger;
import com.iciql.util.StatementBuilder;
import com.iciql.util.StringUtils;
@@ -525,6 +524,17 @@ public class TableDefinition<T> {
throw new IciqlException(e, "failed to get default object for {0}", columnName);
}
+ // identify the type adapter
+ typeAdapter = Utils.getDataTypeAdapter(f.getAnnotations());
+ if (typeAdapter == null) {
+ typeAdapter = Utils.getDataTypeAdapter(f.getType().getAnnotations());
+ }
+
+ if (typeAdapter != null) {
+ DataTypeAdapter<?> dtt = db.getDialect().getAdapter(typeAdapter);
+ dataType = dtt.getDataType();
+ }
+
boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class);
if (hasAnnotation) {
IQColumn col = f.getAnnotation(IQColumn.class);
@@ -538,12 +548,6 @@ public class TableDefinition<T> {
trim = col.trim();
nullable = col.nullable();
- if (col.typeAdapter() != null && col.typeAdapter() != StandardJDBCTypeAdapter.class) {
- typeAdapter = col.typeAdapter();
- DataTypeAdapter<?> dtt = db.getDialect().getAdapter(col.typeAdapter());
- dataType = dtt.getDataType();
- }
-
// annotation overrides
if (!StringUtils.isNullOrEmpty(col.defaultValue())) {
defaultValue = col.defaultValue();
diff --git a/src/main/java/com/iciql/util/Utils.java b/src/main/java/com/iciql/util/Utils.java
index 346c6d7..bf66092 100644
--- a/src/main/java/com/iciql/util/Utils.java
+++ b/src/main/java/com/iciql/util/Utils.java
@@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -40,8 +41,10 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
+import com.iciql.Iciql.DataTypeAdapter;
import com.iciql.Iciql.EnumId;
import com.iciql.Iciql.EnumType;
+import com.iciql.Iciql.TypeAdapter;
import com.iciql.IciqlException;
/**
@@ -532,4 +535,24 @@ public class Utils {
in.close();
}
}
+
+ /**
+ * Identify the data type adapter class in the annotations.
+ *
+ * @param annotations
+ * @return null or the dtaa type adapter class
+ */
+ public static Class<? extends DataTypeAdapter<?>> getDataTypeAdapter(Annotation [] annotations) {
+ Class<? extends DataTypeAdapter<?>> typeAdapter = null;
+ if (annotations != null) {
+ for (Annotation annotation : annotations) {
+ if (annotation instanceof TypeAdapter) {
+ typeAdapter = ((TypeAdapter) annotation).value();
+ } else if (annotation.annotationType().isAnnotationPresent(TypeAdapter.class)) {
+ typeAdapter = annotation.annotationType().getAnnotation(TypeAdapter.class).value();
+ }
+ }
+ }
+ return typeAdapter;
+ }
}
diff --git a/src/site/dta.mkd b/src/site/dta.mkd
new file mode 100644
index 0000000..61c5f62
--- /dev/null
+++ b/src/site/dta.mkd
@@ -0,0 +1,104 @@
+## Data Type Adapters
+
+Data type adapters allow you to extend Iciql's support for field data types.
+
+For example, you might want to take advantage of the [Postgres JSON/JSONB support](http://www.postgresql.org/docs/9.4/static/datatype-json.html) in 9.3/9.4 but instead of directly handling JSON text documents you might want to represent that JSON as a domain object and serialize/deserialize to JSON only when executing an SQL operation.
+
+Data type adapters give you this flexibility.
+
+**NOTE:** Data type adapters are reused within a Db instance and are not inherently thread-safe. You must handle thread-safety on your own, if it is an issue.
+
+### An example
+
+Consider the following model class.
+
+---JAVA---
+@IQTable
+public class Invoices {
+
+ @IQColumn(primaryKey = true, autoIncrement = true)
+ public long _id;
+
+ @IQColumn
+ Date received;
+
+ @IQColumn
+ @TypeAdapter(InvoiceAdapterImpl.class)
+ Invoice invoice;
+}
+---JAVA---
+
+This is a really simple table with three columns, but the third column uses a type adapter to map our *invoice* object field to an SQL type. You can use the `@TypeAdapter` annotation either on the field definition or on the class definition of your domain model.
+
+Let's take a look at *InvoiceAdapterImpl*.
+
+---JAVA---
+public class InvoiceAdapterImpl implements DataTypeAdapter<Invoice> {
+
+ @Override
+ public String getDataType() {
+ return "jsonb";
+ }
+
+ @Override
+ public Class<Invoice> getJavaType() {
+ return Invoice.class;
+ }
+
+ Gson gson() {
+ return new GsonBuilder().create();
+ }
+
+ @Override
+ public Object serialize(DcKey value) {
+ String json = gson().toJson(value);
+ PGobject pg = new PGobject();
+ pg.setType(getDataType());
+ try {
+ pg.setValue(json);
+ } catch (SQLException e) {
+ // ignore, never thrown
+ }
+ return pg;
+ }
+
+ @Override
+ public Invoice deserialize(Object value) {
+
+ // the incoming object is always represented as a string
+ final String json = value.toString();
+ final Invoice invoice = gson().fromJson(json, getJavaType());
+
+ return invoice;
+ }
+---JAVA---
+
+Here you can see how the *InvoiceTypeAdapter* defines a [Postgres JSONB data type](http://www.postgresql.org/docs/9.4/static/datatype-json.html) and automatically handles JSON (de)serialization with [Google Gson](https://code.google.com/p/google-gson) so that the database gets the content in a form that it requires but we can continue to work with objects in Java.
+
+### Custom annotations
+
+It is a little verbose to repeat `@TypeAdapter(InvoiceAdapterImpl.class)` everywhere you want to use your adapter.
+
+To simplify this, you can implement your own annotation which specifies your type adapter.
+
+---JAVA---
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
+@TypeAdapter(InvoiceAdapterImpl.class)
+public @interface InvoiceAdapter { }
+---JAVA---
+
+### Included DataTypeAdapters
+
+Not every DataTypeAdapter has to be a custom implementation.
+The following adapters are included in Iciql for general purpose use.
+
+- `com.iciql.JavaSerializationTypeAdapter`
+Uses Java serialization to store/retrieve your objects as BLOBs.
+- `com.iciql.SQLDialectPostgreSQL.JsonStringAdapter`
+Allows you to use the Postgres JSON data type with a standard String.
+- `com.iciql.SQLDialectPostgreSQL.JsonbStringAdapter`
+Allows you to use the Postgres JSONB data type with a standard String.
+- `com.iciql.SQLDialectPostgreSQL.XmlStringAdapter`
+Allows you to use the Postgres XML data type with a standard String.
+
diff --git a/src/test/java/com/iciql/test/DataTypeAdapterTest.java b/src/test/java/com/iciql/test/DataTypeAdapterTest.java
index f10d298..d1ccb53 100644
--- a/src/test/java/com/iciql/test/DataTypeAdapterTest.java
+++ b/src/test/java/com/iciql/test/DataTypeAdapterTest.java
@@ -16,6 +16,10 @@
package com.iciql.test;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.util.Date;
import org.junit.After;
@@ -26,6 +30,7 @@ import org.junit.Test;
import com.iciql.Db;
import com.iciql.Iciql.IQColumn;
import com.iciql.Iciql.IQTable;
+import com.iciql.Iciql.TypeAdapter;
import com.iciql.JavaSerializationTypeAdapter;
import com.iciql.test.models.SupportedTypes;
@@ -64,17 +69,18 @@ public class DataTypeAdapterTest extends Assert {
}
- @IQTable
+ @IQTable(name="dataTypeAdapters")
public static class SerializedObjectTypeAdapterTest {
@IQColumn(autoIncrement = true, primaryKey = true)
private long id;
@IQColumn
- private java.util.Date received;
+ public java.util.Date received;
- @IQColumn(typeAdapter = SupportedTypesAdapter.class)
- private SupportedTypes obj;
+ @IQColumn
+ @SupportedTypesAdapter
+ public SupportedTypes obj;
}
@@ -82,7 +88,7 @@ public class DataTypeAdapterTest extends Assert {
* Maps a SupportedType instance to a BLOB using Java Object serialization.
*
*/
- public static class SupportedTypesAdapter extends JavaSerializationTypeAdapter<SupportedTypes> {
+ public static class SupportedTypesAdapterImpl extends JavaSerializationTypeAdapter<SupportedTypes> {
@Override
public Class<SupportedTypes> getJavaType() {
@@ -91,4 +97,9 @@ public class DataTypeAdapterTest extends Assert {
}
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
+ @TypeAdapter(SupportedTypesAdapterImpl.class)
+ public @interface SupportedTypesAdapter { }
+
}