diff options
author | James Moger <james.moger@gitblit.com> | 2014-11-07 09:28:57 -0500 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2014-11-09 11:15:14 -0500 |
commit | db0d58c22a0bd4fa2baf023428599757aa4db381 (patch) | |
tree | 9199c08f6b4c4eb217cf6ebb732d8ce5d9856a46 | |
parent | 468775706732f3e85af878710aaf95c81f5b60f2 (diff) | |
download | iciql-db0d58c22a0bd4fa2baf023428599757aa4db381.tar.gz iciql-db0d58c22a0bd4fa2baf023428599757aa4db381.zip |
Revise type adapter definition to be a separate annotation
-rw-r--r-- | build.xml | 1 | ||||
-rw-r--r-- | src/main/java/com/iciql/Iciql.java | 46 | ||||
-rw-r--r-- | src/main/java/com/iciql/SQLDialectPostgreSQL.java | 33 | ||||
-rw-r--r-- | src/main/java/com/iciql/TableDefinition.java | 18 | ||||
-rw-r--r-- | src/main/java/com/iciql/util/Utils.java | 23 | ||||
-rw-r--r-- | src/site/dta.mkd | 104 | ||||
-rw-r--r-- | src/test/java/com/iciql/test/DataTypeAdapterTest.java | 21 |
7 files changed, 199 insertions, 47 deletions
@@ -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 { } + } |