Browse Source

Revise type adapter definition to be a separate annotation

tags/v1.5.0
James Moger 9 years ago
parent
commit
db0d58c22a

+ 1
- 0
build.xml View File

@@ -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" />

+ 11
- 35
src/main/java/com/iciql/Iciql.java View File

@@ -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;
}
/**
@@ -740,6 +732,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,
@@ -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;
}
}
}

+ 33
- 0
src/main/java/com/iciql/SQLDialectPostgreSQL.java View File

@@ -139,6 +139,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.
*/

+ 11
- 7
src/main/java/com/iciql/TableDefinition.java View File

@@ -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();

+ 23
- 0
src/main/java/com/iciql/util/Utils.java View File

@@ -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;
}
}

+ 104
- 0
src/site/dta.mkd View File

@@ -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.

+ 16
- 5
src/test/java/com/iciql/test/DataTypeAdapterTest.java View File

@@ -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 { }

}

Loading…
Cancel
Save