@@ -14,6 +14,7 @@ r23: { | |||
- Improved automatic date conversions | |||
- Revised data type adapters to be specified separately with the @TypeAdapter annotation | |||
additions: | |||
- Add runtime mode support (DEV, TEST, & PROD) | |||
- Add a DAO feature similar to JDBI | |||
- Added Gson, XStream, and SnakeYaml type adapters | |||
dependencyChanges: ~ |
@@ -41,6 +41,7 @@ import com.iciql.DbUpgrader.DefaultDbUpgrader; | |||
import com.iciql.Iciql.IQTable; | |||
import com.iciql.Iciql.IQVersion; | |||
import com.iciql.Iciql.IQView; | |||
import com.iciql.Iciql.Mode; | |||
import com.iciql.util.IciqlLogger; | |||
import com.iciql.util.JdbcUtils; | |||
import com.iciql.util.StringUtils; | |||
@@ -64,6 +65,7 @@ public class Db implements AutoCloseable { | |||
private static final Map<String, Class<? extends SQLDialect>> DIALECTS; | |||
private final Connection conn; | |||
private final Mode mode; | |||
private final Map<Class<?>, TableDefinition<?>> classMap = Collections | |||
.synchronizedMap(new HashMap<Class<?>, TableDefinition<?>>()); | |||
private final SQLDialect dialect; | |||
@@ -88,8 +90,9 @@ public class Db implements AutoCloseable { | |||
DIALECTS.put("SQLite", SQLDialectSQLite.class); | |||
} | |||
private Db(Connection conn) { | |||
private Db(Connection conn, Mode mode) { | |||
this.conn = conn; | |||
this.mode = mode; | |||
String databaseName = null; | |||
try { | |||
DatabaseMetaData data = conn.getMetaData(); | |||
@@ -148,50 +151,81 @@ public class Db implements AutoCloseable { | |||
} | |||
public static Db open(String url) { | |||
return open(url, Mode.PROD); | |||
} | |||
public static Db open(String url, Mode mode) { | |||
try { | |||
Connection conn = JdbcUtils.getConnection(null, url, null, null); | |||
return new Db(conn); | |||
return new Db(conn, mode); | |||
} catch (SQLException e) { | |||
throw new IciqlException(e); | |||
} | |||
} | |||
public static Db open(String url, String user, String password) { | |||
return open(url, user, password, Mode.PROD); | |||
} | |||
public static Db open(String url, String user, String password, Mode mode) { | |||
try { | |||
Connection conn = JdbcUtils.getConnection(null, url, user, password); | |||
return new Db(conn); | |||
return new Db(conn, mode); | |||
} catch (SQLException e) { | |||
throw new IciqlException(e); | |||
} | |||
} | |||
public static Db open(String url, String user, char[] password) { | |||
return open(url, user, password, Mode.PROD); | |||
} | |||
public static Db open(String url, String user, char[] password, Mode mode) { | |||
try { | |||
Connection conn = JdbcUtils.getConnection(null, url, user, password == null ? null : new String(password)); | |||
return new Db(conn); | |||
return new Db(conn, mode); | |||
} catch (SQLException e) { | |||
throw new IciqlException(e); | |||
} | |||
} | |||
public static Db open(DataSource ds) { | |||
return open(ds, Mode.PROD); | |||
} | |||
/** | |||
* Create a new database instance using a data source. This method is fast, | |||
* so that you can always call open() / close() on usage. | |||
* | |||
* @param ds | |||
* the data source | |||
* @param mode | |||
* the runtime mode | |||
* @return the database instance. | |||
*/ | |||
public static Db open(DataSource ds) { | |||
public static Db open(DataSource ds, Mode mode) { | |||
try { | |||
return new Db(ds.getConnection()); | |||
return new Db(ds.getConnection(), mode); | |||
} catch (SQLException e) { | |||
throw new IciqlException(e); | |||
} | |||
} | |||
public static Db open(Connection conn) { | |||
return new Db(conn); | |||
return open(conn, Mode.PROD); | |||
} | |||
public static Db open(Connection conn, Mode mode) { | |||
return new Db(conn, mode); | |||
} | |||
/** | |||
* Returns the Iciql runtime mode. | |||
* | |||
* @return the runtime mode | |||
*/ | |||
public Mode getMode() { | |||
return mode; | |||
} | |||
/** | |||
@@ -806,4 +840,17 @@ public class Db implements AutoCloseable { | |||
return this.autoSavePoint; | |||
} | |||
/** | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
class NoExternalDaoStatements implements DaoStatementProvider { | |||
@Override | |||
public String getStatement(String idOrStatement) { | |||
return idOrStatement; | |||
} | |||
} | |||
} |
@@ -726,6 +726,25 @@ public interface Iciql { | |||
public @interface IQIgnore{ | |||
} | |||
/** | |||
* The runtime mode for Iciql. | |||
*/ | |||
public static enum Mode { | |||
DEV, TEST, PROD; | |||
public static Mode fromValue(String value) { | |||
for (Mode mode : values()) { | |||
if (mode.name().equalsIgnoreCase(value)) { | |||
return mode; | |||
} | |||
} | |||
return PROD; | |||
} | |||
} | |||
/** | |||
* This method is called to let the table define the primary key, indexes, | |||
* and the table name. | |||
@@ -767,6 +786,18 @@ public interface Iciql { | |||
*/ | |||
Class<T> getJavaType(); | |||
/** | |||
* Set the runtime mode. | |||
* <p> | |||
* Allows type adapters to adapt type mappings based on the runtime | |||
* mode. | |||
* </p> | |||
* | |||
* @param mode | |||
*/ | |||
void setMode(Mode mode); | |||
/** | |||
* Serializes your Java object into a JDBC object. | |||
* |
@@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap; | |||
import com.iciql.Iciql.ConstraintDeleteType; | |||
import com.iciql.Iciql.ConstraintUpdateType; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
import com.iciql.TableDefinition.ConstraintForeignKeyDefinition; | |||
import com.iciql.TableDefinition.ConstraintUniqueDefinition; | |||
import com.iciql.TableDefinition.FieldDefinition; | |||
@@ -50,6 +51,7 @@ public class SQLDialectDefault implements SQLDialect { | |||
int databaseMinorVersion; | |||
String databaseName; | |||
String productVersion; | |||
Mode mode; | |||
Map<Class<? extends DataTypeAdapter<?>>, DataTypeAdapter<?>> typeAdapters; | |||
public SQLDialectDefault() { | |||
@@ -76,6 +78,8 @@ public class SQLDialectDefault implements SQLDialect { | |||
} catch (SQLException e) { | |||
throw new IciqlException(e, "failed to retrieve database metadata!"); | |||
} | |||
mode = db.getMode(); | |||
} | |||
@Override | |||
@@ -446,12 +450,13 @@ public class SQLDialectDefault implements SQLDialect { | |||
@Override | |||
public DataTypeAdapter<?> getAdapter(Class<? extends DataTypeAdapter<?>> typeAdapter) { | |||
DataTypeAdapter<?> dtt = typeAdapters.get(typeAdapter); | |||
if (dtt == null) { | |||
dtt = Utils.newObject(typeAdapter); | |||
typeAdapters.put(typeAdapter, dtt); | |||
DataTypeAdapter<?> dta = typeAdapters.get(typeAdapter); | |||
if (dta == null) { | |||
dta = Utils.newObject(typeAdapter); | |||
typeAdapters.put(typeAdapter, dta); | |||
} | |||
return dtt; | |||
dta.setMode(mode); | |||
return dta; | |||
} | |||
@SuppressWarnings("unchecked") | |||
@@ -462,19 +467,19 @@ public class SQLDialectDefault implements SQLDialect { | |||
return value; | |||
} | |||
DataTypeAdapter<T> dtt = (DataTypeAdapter<T>) getAdapter(typeAdapter); | |||
return dtt.serialize(value); | |||
DataTypeAdapter<T> dta = (DataTypeAdapter<T>) getAdapter(typeAdapter); | |||
return dta.serialize(value); | |||
} | |||
@Override | |||
public Object deserialize(Object value, Class<? extends DataTypeAdapter<?>> typeAdapter) { | |||
DataTypeAdapter<?> dtt = typeAdapters.get(typeAdapter); | |||
if (dtt == null) { | |||
dtt = Utils.newObject(typeAdapter); | |||
typeAdapters.put(typeAdapter, dtt); | |||
if (typeAdapter == null) { | |||
// pass-through | |||
return value; | |||
} | |||
return dtt.deserialize(value); | |||
DataTypeAdapter<?> dta = getAdapter(typeAdapter); | |||
return dta.deserialize(value); | |||
} | |||
@Override |
@@ -18,6 +18,7 @@ package com.iciql.adapter; | |||
import com.google.gson.Gson; | |||
import com.google.gson.GsonBuilder; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
/** | |||
* Base class for inserting/retrieving a Java Object (de)serialized as JSON | |||
@@ -39,10 +40,17 @@ import com.iciql.Iciql.DataTypeAdapter; | |||
*/ | |||
public abstract class GsonTypeAdapter<T> implements DataTypeAdapter<T> { | |||
protected Mode mode; | |||
protected Gson gson() { | |||
return new GsonBuilder().create(); | |||
} | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "TEXT"; |
@@ -25,9 +25,9 @@ import java.io.ObjectOutputStream; | |||
import java.sql.Blob; | |||
import java.sql.SQLException; | |||
import com.iciql.Iciql; | |||
import com.iciql.IciqlException; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
import com.iciql.IciqlException; | |||
/** | |||
* Base class for inserting/retrieving a Java Object as a BLOB field using Java Serialization. | |||
@@ -44,6 +44,13 @@ import com.iciql.Iciql.DataTypeAdapter; | |||
*/ | |||
public abstract class JavaSerializationTypeAdapter<T> implements DataTypeAdapter<T> { | |||
protected Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public final String getDataType() { | |||
return "BLOB"; |
@@ -21,16 +21,24 @@ import org.yaml.snakeyaml.Yaml; | |||
import org.yaml.snakeyaml.nodes.Tag; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
/** | |||
* Base class for inserting/retrieving a Java Object (de)serialized as YAML using SnakeYaml. | |||
*/ | |||
public abstract class SnakeYamlTypeAdapter<T> implements DataTypeAdapter<T> { | |||
protected Mode mode; | |||
protected Yaml yaml() { | |||
return new Yaml(); | |||
} | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "TEXT"; |
@@ -17,6 +17,7 @@ | |||
package com.iciql.adapter; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
import com.thoughtworks.xstream.XStream; | |||
/** | |||
@@ -24,10 +25,17 @@ import com.thoughtworks.xstream.XStream; | |||
*/ | |||
public class XStreamTypeAdapter implements DataTypeAdapter<Object> { | |||
protected Mode mode; | |||
protected XStream xstream() { | |||
return new XStream(); | |||
} | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "TEXT"; |
@@ -20,12 +20,20 @@ import java.sql.SQLException; | |||
import org.postgresql.util.PGobject; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
/** | |||
* Handles transforming raw strings to/from the Postgres JSON data type. | |||
*/ | |||
public class JsonStringAdapter implements DataTypeAdapter<String> { | |||
protected Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "json"; |
@@ -20,12 +20,20 @@ import java.sql.SQLException; | |||
import org.postgresql.util.PGobject; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
/** | |||
* Handles transforming raw strings to/from the Postgres JSONB data type. | |||
*/ | |||
public class JsonbStringAdapter implements DataTypeAdapter<String> { | |||
protected Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "jsonb"; |
@@ -20,12 +20,20 @@ import java.sql.SQLException; | |||
import org.postgresql.util.PGobject; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
/** | |||
* Handles transforming raw strings to/from the Postgres XML data type. | |||
*/ | |||
public class XmlStringAdapter implements DataTypeAdapter<String> { | |||
protected Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "xml"; |
@@ -35,6 +35,13 @@ Let's take a look at *InvoiceAdapterImpl*. | |||
---JAVA--- | |||
public class InvoiceAdapterImpl implements DataTypeAdapter<Invoice> { | |||
Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "jsonb"; | |||
@@ -76,6 +83,10 @@ public class InvoiceAdapterImpl implements DataTypeAdapter<Invoice> { | |||
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. | |||
### Runtime Mode | |||
Data type adapters can respond to the Iciql runtime mode (`DEV`, `TEST`, or `PROD`) allowing them to change their behavior. This is useful for targetting a data type that might be available in your production database but may not be available in your development or testing database. | |||
### Custom annotations | |||
It is a little verbose to repeat `@TypeAdapter(InvoiceAdapterImpl.class)` everywhere you want to use your adapter. |