summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2013-08-07 23:53:53 +0200
committerTrygve Laugstøl <trygvis@inamo.no>2013-08-07 23:53:53 +0200
commit26b01b500065634eb3133dc354a0ba71b13bff56 (patch)
tree2fed1b329f421b7da7bf6c223f17fad230d1b5bd
parentdd150071369e825d4b4a59e15ad3291841c7ba13 (diff)
downloadcontainer-playground-26b01b500065634eb3133dc354a0ba71b13bff56.tar.gz
container-playground-26b01b500065634eb3133dc354a0ba71b13bff56.tar.bz2
container-playground-26b01b500065634eb3133dc354a0ba71b13bff56.tar.xz
container-playground-26b01b500065634eb3133dc354a0ba71b13bff56.zip
wip
o Start of JPA implementation.
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/container/compiler/EntityHandler.java183
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/container/compiler/MyProcessor.java10
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/container/compiler/SqlUnitModel.java9
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/container/compiler/TransactionalHandler.java5
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/container/compiler/Utils.java46
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/ClassG.java34
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/Constructor.java11
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/MethodRef.java17
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/TypeRef.java21
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/persistence/EntityMirror.java75
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/persistence/SqlEntitySet.java1
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/EntityManagerFactoryGenerator.java69
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/EntityManagerGenerator.java129
-rw-r--r--container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/SequencesGenerator.java41
-rw-r--r--container-compiler-plugin/src/test/java/io/trygvis/container/compiler/InMemoryJavaFileManager.java2
-rw-r--r--container-compiler-plugin/src/test/java/io/trygvis/container/compiler/ProcessorTest.java3
-rw-r--r--container-compiler-plugin/src/test/java/io/trygvis/persistence/EntityMirrorTest.java5
-rw-r--r--container-compiler-plugin/src/test/resources/io/trygvis/persistence/test/package-info.java2
-rw-r--r--myapp/src/main/java/io/trygvis/container/myapp/AddressBookDirect.java (renamed from myapp/src/main/java/io/trygvis/container/myapp/AddressBook.java)13
-rw-r--r--myapp/src/main/java/io/trygvis/container/myapp/AddressBookJpa.java242
-rw-r--r--myapp/src/main/java/io/trygvis/container/myapp/DriverManagerDataSource.java60
-rw-r--r--myapp/src/main/java/io/trygvis/container/myapp/package-info.java2
-rw-r--r--sql-persistence/src/main/java/io/trygvis/persistence/sql/AbstractTypedQuery.java12
-rw-r--r--sql-persistence/src/main/java/io/trygvis/persistence/sql/FromResultSet.java8
-rw-r--r--sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlDao.java25
-rw-r--r--sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityDesc.java11
-rw-r--r--sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManager.java419
-rw-r--r--sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManagerFactory.java105
-rw-r--r--sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityMeta.java15
-rw-r--r--sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlExecutor.java19
-rw-r--r--sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlQuery.java249
-rw-r--r--sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlUnit.java18
32 files changed, 1705 insertions, 156 deletions
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/EntityHandler.java b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/EntityHandler.java
index 74f303f..9bce51e 100644
--- a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/EntityHandler.java
+++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/EntityHandler.java
@@ -1,15 +1,20 @@
package io.trygvis.container.compiler;
import io.trygvis.container.compiler.model.ClassG;
+import io.trygvis.container.compiler.model.Constructor;
import io.trygvis.container.compiler.model.FieldRef;
+import io.trygvis.container.compiler.model.MethodRef;
+import io.trygvis.container.compiler.model.Parameters;
import io.trygvis.container.compiler.model.TypeRef;
import io.trygvis.persistence.EntityMirror;
import io.trygvis.persistence.FieldMirror;
import io.trygvis.persistence.GeneratorConfiguration;
import io.trygvis.persistence.SequenceMirror;
import io.trygvis.persistence.SqlEntity;
+import io.trygvis.persistence.SqlEntitySet;
import io.trygvis.persistence.TypeHandler;
-import io.trygvis.persistence.sql.SqlEntityDesc;
+import io.trygvis.persistence.sql.SqlDao;
+import io.trygvis.persistence.sql.SqlEntityMeta;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
@@ -25,9 +30,10 @@ import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
-import javax.tools.JavaFileObject;
import java.io.IOException;
-import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -35,8 +41,9 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
-import static io.trygvis.container.compiler.Utils.toFieldName;
-import static io.trygvis.container.compiler.Utils.toJavaString;
+import static io.trygvis.container.compiler.Utils.*;
+import static io.trygvis.container.compiler.model.Parameters.ParameterRef;
+import static io.trygvis.container.compiler.model.TypeRef.VOID;
import static io.trygvis.persistence.FieldMirror.AccessorType.FIELD;
import static io.trygvis.persistence.FieldMirror.AccessorType.METHOD;
import static io.trygvis.persistence.FieldMirror.FieldType.PRIMITIVE;
@@ -47,15 +54,15 @@ import static java.lang.String.format;
import static java.lang.reflect.Modifier.PUBLIC;
import static javax.lang.model.util.ElementFilter.fieldsIn;
import static javax.lang.model.util.ElementFilter.methodsIn;
-import static org.apache.commons.lang.StringUtils.*;
+import static org.apache.commons.lang.StringUtils.join;
public class EntityHandler extends AbstractHandler {
private GeneratorConfiguration generatorConfiguration = new GeneratorConfiguration();
- private SqlUnitModel sqlUnit = new SqlUnitModel();
- private PackageElement packageElement;
+ private SqlUnitModel sqlUnit;
- public EntityHandler(ProcessingEnvironment processingEnv) {
+ public EntityHandler(ProcessingEnvironment processingEnv, SqlUnitModel sqlUnit) {
super(processingEnv);
+ this.sqlUnit = sqlUnit;
}
public void phase1(Set<TypeElement> sqlEntities, Set<PackageElement> packages) throws Exception {
@@ -81,11 +88,22 @@ public class EntityHandler extends AbstractHandler {
if (packages.size() == 0) {
throw new CompilerException("There has to be exactly one @SqlEntitySet annotated package.");
}
- packageElement = packages.iterator().next();
+ PackageElement packageElement = packages.iterator().next();
if (packages.size() != 1) {
throw new CompilerException(packageElement, "There can only be one @SqlEntitySet annotated package.");
}
+ AnnotationMirror sqlEntitySet = getAnnotation(SqlEntitySet.class, packageElement);
+ String name = null;
+ for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> v : sqlEntitySet.getElementValues().entrySet()) {
+ String field = v.getKey().getSimpleName().toString();
+ String value = v.getValue().getValue().toString();
+ switch (field) {
+ case "name":
+ name = value;
+ }
+ }
+ sqlUnit.setName(name);
sqlUnit.setPackageName(packageElement.getQualifiedName().toString());
}
@@ -178,6 +196,7 @@ public class EntityHandler extends AbstractHandler {
if (idFields.size() != 1) {
throw new CompilerException(element, "This implementation only support a single @Id annotated field.");
}
+ entity.setIdType(idFields.get(0).type);
sqlUnit.add(entity, element);
}
@@ -193,18 +212,19 @@ public class EntityHandler extends AbstractHandler {
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> v : sequenceGenerator.getElementValues().entrySet()) {
String field = v.getKey().getSimpleName().toString();
+ String value = v.getValue().getValue().toString();
switch (field) {
case "name":
- name = v.getValue().getValue().toString();
+ name = value;
break;
case "sequenceName":
- sequenceName = v.getValue().getValue().toString();
+ sequenceName = value;
break;
case "initialValue":
- initialValue = Integer.valueOf(v.getValue().getValue().toString());
+ initialValue = Integer.valueOf(value);
break;
case "allocationSize":
- allocationSize = Integer.valueOf(v.getValue().getValue().toString());
+ allocationSize = Integer.valueOf(value);
break;
default:
throw new InternalErrorException("Unsupported field on @SequenceGenerator: " + field);
@@ -218,30 +238,83 @@ public class EntityHandler extends AbstractHandler {
}
public ClassG phase3(EntityMirror entityMirror) throws IOException {
- ClassG g = new ClassG(PUBLIC, entityMirror.daoType);
+ ClassG g = new ClassG(PUBLIC, entityMirror.daoType).
+ extendsType(new TypeRef(SqlDao.class).args(entityMirror.idType, entityMirror.type));
+ Parameters p = new Parameters();
+ ParameterRef c = p.addParameter(new TypeRef(Connection.class), "c");
+ g.add(new Constructor(p, "super(" + c.name + ");"));
TypeRef stringType = g.imports.add(String.class);
- TypeRef sqlEntityDescType = g.imports.add(SqlEntityDesc.class);
+ TypeRef sqlEntityDescType = g.imports.add(SqlEntityMeta.class);
+ TypeRef sqlException = g.imports.add(SQLException.class);
- g.addPublicStaticFinalField(stringType, "createTableSql").
+ FieldRef createTableSql = g.addPublicStaticFinalField(stringType, "createTableSql").
value(toJavaString(entityMirror.createTableSql(sqlUnit)));
- g.addPublicStaticFinalField(stringType, "dropTableSql").
+ g.add(new MethodRef(PUBLIC, stringType, "createTableSql", "return createTableSql;"));
+ FieldRef dropTableSql = g.addPublicStaticFinalField(stringType, "dropTableSql").
value(toJavaString(entityMirror.dropTableSql()));
+ g.add(new MethodRef(PUBLIC, stringType, "dropTableSql", "return dropTableSql;"));
g.addPublicStaticFinalField(stringType, "insertIntoSql").
value(toJavaString(entityMirror.insertIntoSql(sqlUnit)));
g.addPublicStaticFinalField(stringType, "deleteFromSql").
value(toJavaString(entityMirror.deleteFromSql()));
String desc = "new " + sqlEntityDescType + "(" +
toJavaString(entityMirror.tableName) + ", " +
- toJavaString(entityMirror.defaultFields()) +
+ toJavaString(entityMirror.defaultFields()) + ", " +
+ toJavaString(createTableSql.name) + ", " +
+ toJavaString(dropTableSql.name) +
")";
g.addPublicStaticFinalField(sqlEntityDescType, "desc").value(desc);
ClassG.InnerClassG typedQuery = g.addInnerClass(entityMirror.queryType(g.imports));
- typedQuery.inner.addMethod(entityMirror.fromResultSet(g.imports));
- g.addMethod(entityMirror.insertInto(sqlUnit, g.imports));
- g.addMethod(entityMirror.delete(g.imports));
- g.addMethod(entityMirror.deleteById(g.imports));
- g.addMethod(entityMirror.query(g.imports));
+ g.addInnerClass(entityMirror.utils(sqlUnit));
+
+ {
+ p = new Parameters();
+ ParameterRef rs = p.addParameter(new TypeRef(ResultSet.class), "rs");
+ g.add(new MethodRef(PUBLIC, entityMirror.type, "fromResultSet", p,
+ "return Utils.fromResultSet" + entityMirror.type.className + "(" + rs.name + ");").
+ exception(sqlException));
+ }
+
+ {
+ p = new Parameters();
+ ParameterRef entity = p.addParameter(entityMirror.type, "entity");
+ g.add(new MethodRef(PUBLIC, VOID, "insert", p,
+ "Utils.insert" + entityMirror.type.className + "(super.c, " + entity.name + ");").
+ exception(sqlException));
+ }
+
+ {
+ p = new Parameters();
+ ParameterRef id = p.addParameter(entityMirror.idType, "id");
+ g.add(new MethodRef(PUBLIC, entityMirror.type, "selectById", p,
+ "return Utils.select" + entityMirror.type.className + "ById(super.c, " + id.name + ");").
+ exception(sqlException));
+ }
+
+ {
+ p = new Parameters();
+ ParameterRef entity = p.addParameter(entityMirror.type, "entity");
+ g.add(new MethodRef(PUBLIC, VOID, "delete", p,
+ "Utils.delete" + entityMirror.type.className + "(super.c, " + entity.name + ");").
+ exception(sqlException));
+ }
+
+ {
+ p = new Parameters();
+ ParameterRef id = p.addParameter(entityMirror.idType, "id");
+ g.add(new MethodRef(PUBLIC, VOID, "deleteById", p,
+ "Utils.delete" + entityMirror.type.className + "ById(super.c, " + id.name + ");").
+ exception(sqlException));
+ }
+
+ {
+ p = new Parameters();
+ ParameterRef entity = p.addParameter(entityMirror.type, "entity");
+ g.add(new MethodRef(PUBLIC, VOID, "update", p,
+ "Utils.update" + entityMirror.type.className + "(super.c, " + entity.name + ");").
+ exception(sqlException));
+ }
return g;
}
@@ -328,10 +401,8 @@ public class EntityHandler extends AbstractHandler {
public void phase3(boolean errorRaised) throws Exception {
try {
for (EntityMirror entity : sqlUnit.getEntities().values()) {
- writeFile(phase3(entity), sqlUnit.element(entity));
+ writeFile(processingEnv, phase3(entity), sqlUnit.element(entity));
}
- writeFile(generateSequences(sqlUnit), null);
- writeFile(generateSession(), null);
} catch (CompilerException | InternalErrorException e) {
// Ignore any exceptions if we had an error from before
if (errorRaised) {
@@ -341,62 +412,6 @@ public class EntityHandler extends AbstractHandler {
}
}
- private ClassG generateSequences(SqlUnitModel unit) {
- TypeRef sequences = new TypeRef(unit.getPackageName() + ".Sequences");
- ClassG g = new ClassG(PUBLIC, sequences);
- List<String> creates = new ArrayList<>();
- List<String> drops = new ArrayList<>();
- for (SequenceMirror sequence : unit.getSequences().values()) {
- TypeRef stringType = g.imports.add(String.class);
- String value = "CREATE SEQUENCE " + sequence.sequenceName + ";";
- FieldRef f = g.addPublicStaticFinalField(stringType, "create" + capitalize(sequence.name)).
- value(toJavaString(value));
- creates.add(f.name);
-
- f = g.addPublicStaticFinalField(stringType, "drop" + capitalize(sequence.name)).
- value(toJavaString("DROP SEQUENCE " + sequence.sequenceName) + ";");
- drops.add(f.name);
-
- }
- g.addPublicStaticFinalField(new TypeRef(String[].class), "createSequences").
- value("new String[]{" + join(creates, ", ") + "}");
- g.addPublicStaticFinalField(new TypeRef(String[].class), "dropSequences").
- value("new String[]{" + join(drops, ", ") + "}");
- return g;
- }
-
- private void writeFile(ClassG g, Element element) throws IOException {
- JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(g.type.fqName, element);
- try (PrintWriter w = new PrintWriter(sourceFile.openWriter())) {
- for (String s : g.generate()) {
- w.println(stripEnd(s, " "));
- }
- }
- }
-
- private ClassG generateSession() throws IOException {
- String p = packageElement.getQualifiedName().toString();
-
- // TODO: Support a name prefix from @SqlEntitySet
- TypeRef type = new TypeRef(p + ".Session");
-
-/*
- TypeRef conType = g.add(Connection.class);
- Parameters parameters = new Parameters();
- ParameterRef c = parameters.addParameter(conType, "c");
-
- List<String> body = new ArrayList<>();
- for (EntityMirror entity : entities) {
- FieldRef fieldRef = g.addField(entity.javaName.asElement().asType(), toFieldName(entity.daoName));
- body.add("this." + fieldRef.name + " = new " + entity.daoName + "(" + c.name + ");");
- }
-
- g.addConstructor(parameters, body);
-*/
-
- return new ClassG(PUBLIC, type);
- }
-
public static String sqlName(String javaName) {
StringBuilder builder = new StringBuilder();
for (char c : javaName.toCharArray()) {
@@ -420,7 +435,7 @@ public class EntityHandler extends AbstractHandler {
return null;
}
- public AnnotationMirror getAnnotation(Class<?> c, TypeElement type) {
+ public AnnotationMirror getAnnotation(Class<?> c, Element type) {
AnnotationMirror annotation = findAnnotation(c, type);
if (annotation != null) {
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/MyProcessor.java b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/MyProcessor.java
index fbbc56d..64ac678 100644
--- a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/MyProcessor.java
+++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/MyProcessor.java
@@ -25,6 +25,10 @@ import javax.tools.Diagnostic;
import java.util.HashSet;
import java.util.Set;
+import static io.trygvis.container.compiler.Utils.writeFile;
+import static io.trygvis.persistence.generators.EntityManagerGenerator.generateEntityManager;
+import static io.trygvis.persistence.generators.EntityManagerFactoryGenerator.generateEntityManagerFactory;
+import static io.trygvis.persistence.generators.SequencesGenerator.generateSequences;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
@@ -104,7 +108,8 @@ public class MyProcessor implements Processor {
TypeElement log = elements.getTypeElement(Log.class.getCanonicalName());
TypeElement entity = elements.getTypeElement(Entity.class.getCanonicalName());
- EntityHandler entityHandler = new EntityHandler(processingEnv);
+ SqlUnitModel unit = new SqlUnitModel();
+ EntityHandler entityHandler = new EntityHandler(processingEnv, unit);
Set<TypeElement> sqlEntities = typesIn(roundEnv.getElementsAnnotatedWith(SqlEntity.class));
Set<PackageElement> packages = ElementFilter.packagesIn(roundEnv.getElementsAnnotatedWith(SqlEntitySet.class));
@@ -136,6 +141,9 @@ public class MyProcessor implements Processor {
}
entityHandler.phase3(hadErrors);
+ writeFile(processingEnv, generateSequences(unit), null);
+ writeFile(processingEnv, generateEntityManagerFactory(unit), null);
+ writeFile(processingEnv, generateEntityManager(unit), null);
return true;
}
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/SqlUnitModel.java b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/SqlUnitModel.java
index 1bd2535..e41fbb8 100644
--- a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/SqlUnitModel.java
+++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/SqlUnitModel.java
@@ -14,6 +14,7 @@ public class SqlUnitModel {
private Map<String, SequenceMirror> sequences = new TreeMap<>();
private Map<SequenceMirror, Element> sequenceElements = new TreeMap<>();
private String packageName;
+ private String name;
public String getPackageName() {
if (packageName == null) {
@@ -27,6 +28,14 @@ public class SqlUnitModel {
this.packageName = packageName;
}
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
// -----------------------------------------------------------------------
// Entity Mirrors
// -----------------------------------------------------------------------
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/TransactionalHandler.java b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/TransactionalHandler.java
index b5db075..bfa1ec8 100644
--- a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/TransactionalHandler.java
+++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/TransactionalHandler.java
@@ -1,6 +1,7 @@
package io.trygvis.container.compiler;
import io.trygvis.container.compiler.model.ClassG;
+import io.trygvis.container.compiler.model.Constructor;
import io.trygvis.container.compiler.model.FieldRef;
import io.trygvis.container.compiler.model.MethodRef;
import io.trygvis.container.compiler.model.Parameters;
@@ -109,7 +110,7 @@ public class TransactionalHandler extends AbstractHandler {
body.add(" });");
MethodRef m = new MethodRef(PUBLIC, returnType, ee.getSimpleName().toString(), parameters, body);
- g.addMethod(m);
+ g.add(m);
}
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile((p.length() == 0 ? "" : p + ".") + className, element);
@@ -137,6 +138,6 @@ public class TransactionalHandler extends AbstractHandler {
List<String> body = new ArrayList<>();
body.add("super(" + collectionToDelimitedString(goesToSuper, ", ") + ");");
body.add("this." + platformTransactionManager.name + " = " + transactionManager.name + ";");
- g.addConstructor(parameters, body);
+ g.add(new Constructor(parameters, body));
}
}
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/Utils.java b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/Utils.java
index 8bb1b33..5d69fc0 100644
--- a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/Utils.java
+++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/Utils.java
@@ -1,13 +1,29 @@
package io.trygvis.container.compiler;
+import io.trygvis.container.compiler.model.ClassG;
+import io.trygvis.container.compiler.model.Imports;
+import io.trygvis.container.compiler.model.TypeRef;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.persistence.PersistenceException;
+import javax.tools.JavaFileObject;
import java.io.BufferedReader;
import java.io.IOException;
+import java.io.PrintWriter;
import java.io.StringReader;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import static io.trygvis.container.compiler.model.ClassG.addAll;
import static java.lang.Character.toLowerCase;
import static java.lang.Character.toUpperCase;
+import static org.apache.commons.lang.StringUtils.stripEnd;
public class Utils {
+ public static final String EOL = System.getProperty("line.separator");
+
public static String toFieldName(String s) {
if (s.length() < 1) {
return s.toLowerCase();
@@ -40,6 +56,11 @@ public class Utils {
return new String(chars, 0, j);
}
+ public static String toClassName(String s) {
+ s = toFieldName(s);
+ return toUpperCase(s.charAt(0)) + s.substring(1, s.length());
+ }
+
public static String toSetterName(String s) {
s = toFieldName(s);
return "set" + toUpperCase(s.charAt(0)) + s.substring(1, s.length());
@@ -60,8 +81,8 @@ public class Utils {
buffer.append(line.replace("\"", "\\\""));
buffer.append('"');
line = reader.readLine();
- if(line != null) {
- buffer.append(" +\n");
+ if (line != null) {
+ buffer.append(" +").append(EOL);
}
}
@@ -70,4 +91,25 @@ public class Utils {
throw new RuntimeException(e);
}
}
+
+ public static void writeFile(ProcessingEnvironment processingEnv, ClassG g, Element element) throws IOException {
+ JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(g.type.fqName, element);
+ try (PrintWriter w = new PrintWriter(sourceFile.openWriter())) {
+ for (String s : g.generate()) {
+ w.println(stripEnd(s, " "));
+ }
+ }
+ }
+
+ public static List<String> tryCatchSqlException(Imports imports, List<String> body) {
+ TypeRef sqlException = imports.add(SQLException.class);
+ TypeRef persistenceException = imports.add(PersistenceException.class);
+ List<String> newBody = new ArrayList<>();
+ newBody.add("try {");
+ addAll(1, newBody, body);
+ newBody.add("} catch (" + sqlException.plainName + " e) {");
+ newBody.add(" throw new " + persistenceException.plainName + "(e);"); // TODO: Add context info.
+ newBody.add("}");
+ return newBody;
+ }
}
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/ClassG.java b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/ClassG.java
index c99feae..402498c 100644
--- a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/ClassG.java
+++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/ClassG.java
@@ -45,7 +45,12 @@ public class ClassG {
}
public ClassG extendsType(TypeRef extendsType) {
- this.extendsType = extendsType;
+ this.extendsType = imports.add(extendsType);
+ return this;
+ }
+
+ public ClassG extendsType(Class<?> extendsType) {
+ this.extendsType = imports.add(extendsType);
return this;
}
@@ -54,6 +59,13 @@ public class ClassG {
return this;
}
+ public ClassG implementsType(Class... implementsTypes) {
+ for (Class type : implementsTypes) {
+ this.implementsTypes.add(imports.add(type));
+ }
+ return this;
+ }
+
public FieldRef addField(TypeMirror klass, String name) {
TypeRef type = imports.add(klass);
FieldRef ref = new FieldRef(PRIVATE | FINAL, type, name);
@@ -80,18 +92,17 @@ public class ClassG {
return addField(PUBLIC | STATIC | FINAL, type, name);
}
- public Constructor addConstructor(Parameters parameters, List<String> body) {
- Constructor constructor = new Constructor(this, parameters, body);
+ public ClassG add(Constructor constructor) {
constructors.add(constructor);
- return constructor;
+ return this;
}
- public ClassG addMethod(MethodRef methodRef) {
+ public ClassG add(MethodRef methodRef) {
this.methods.add(methodRef);
return this;
}
-// public MethodRef addMethod(List<String> body, TypeRef returnType, String name, Parameters parameters) {
+// public MethodRef add(List<String> body, TypeRef returnType, String name, Parameters parameters) {
// MethodRef ref = new MethodRef(PUBLIC, returnType, name, parameters, body);
// methods.add(ref);
// return ref;
@@ -145,7 +156,7 @@ public class ClassG {
for (Constructor constructor : constructors) {
body.add("");
- addAll(1, body, constructor.write());
+ addAll(1, body, constructor.write(this));
}
for (MethodRef method : methods) {
@@ -165,7 +176,11 @@ public class ClassG {
}
private List<String> write(MethodRef method) {
- List<String> body = new ArrayList<>();
+ String typeArgs = "";
+ if (!method.typeArgs.isEmpty()) {
+ typeArgs = "<" + join(method.typeArgs, ", ") + "> ";
+ }
+
String returnString;
if (method.returnType == TypeRef.VOID) {
returnString = "void";
@@ -178,9 +193,10 @@ public class ClassG {
parameters.add("final " + p.klass + " " + p.name);
}
- String m = Modifier.toString(method.modifiers) + " " +
+ String m = Modifier.toString(method.modifiers) + " " + typeArgs +
returnString + " " +
method.name + "(" + collectionToDelimitedString(parameters, ", ") + ")";
+ List<String> body = new ArrayList<>();
if (method.exceptions.isEmpty()) {
body.add(m + " {");
} else {
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/Constructor.java b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/Constructor.java
index 0f3ceeb..f701476 100644
--- a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/Constructor.java
+++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/Constructor.java
@@ -4,21 +4,24 @@ import java.util.ArrayList;
import java.util.List;
import static io.trygvis.container.compiler.model.ClassG.addAll;
+import static java.util.Arrays.asList;
import static org.apache.commons.lang.StringUtils.join;
public class Constructor {
- private final ClassG g;
private final Parameters parameters;
private final List<String> body;
- public Constructor(ClassG g, Parameters parameters, List<String> body) {
- this.g = g;
+ public Constructor(Parameters parameters, String... body) {
+ this(parameters, asList(body));
+ }
+
+ public Constructor(Parameters parameters, List<String> body) {
this.parameters = parameters;
this.body = body;
}
- public List<String> write() {
+ public List<String> write(ClassG g) {
List<String> ps = new ArrayList<>();
for (Parameters.ParameterRef p : parameters) {
ps.add(p.klass + " " + p.name);
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/MethodRef.java b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/MethodRef.java
index 902d25a..952a1e5 100644
--- a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/MethodRef.java
+++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/MethodRef.java
@@ -1,11 +1,14 @@
package io.trygvis.container.compiler.model;
import java.lang.reflect.Modifier;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
+import static java.util.Arrays.asList;
import static java.util.Collections.addAll;
+import static java.util.Collections.singletonList;
public class MethodRef {
public final TypeRef returnType;
@@ -13,8 +16,17 @@ public class MethodRef {
public final int modifiers;
public final Parameters parameters;
public final Set<TypeRef> exceptions = new TreeSet<>();
+ public final List<String> typeArgs = new ArrayList<>();
public final List<String> body;
+ public MethodRef(int modifiers, TypeRef returnType, String name, String body) {
+ this(modifiers, returnType, name, new Parameters(), singletonList(body));
+ }
+
+ public MethodRef(int modifiers, TypeRef returnType, String name, Parameters p, String... body) {
+ this(modifiers, returnType, name, p, asList(body));
+ }
+
public MethodRef(int modifiers, TypeRef returnType, String name, Parameters parameters, List<String> body) {
this.modifiers = modifiers;
this.returnType = returnType;
@@ -31,4 +43,9 @@ public class MethodRef {
addAll(this.exceptions, exceptions);
return this;
}
+
+ public MethodRef typeArgs(String... args) {
+ addAll(typeArgs, args);
+ return this;
+ }
}
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/TypeRef.java b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/TypeRef.java
index 2e679a5..c329ab3 100644
--- a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/TypeRef.java
+++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/TypeRef.java
@@ -2,6 +2,7 @@ package io.trygvis.container.compiler.model;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -39,9 +40,9 @@ public class TypeRef implements Comparable<TypeRef> {
public final boolean array;
- public final List<TypeRef> args;
+ public final List<String> args;
- private TypeRef(String fqName, String name, boolean primitive, boolean array, List<TypeRef> args) {
+ private TypeRef(String fqName, String name, boolean primitive, boolean array, List<String> args) {
this.fqName = fqName;
this.plainName = name;
this.primitive = primitive;
@@ -61,7 +62,7 @@ public class TypeRef implements Comparable<TypeRef> {
}
public TypeRef(Class<?> klass) {
- this(fqName(klass), fqName(klass), false, klass.isArray(), Collections.<TypeRef>emptyList());
+ this(fqName(klass), fqName(klass), false, klass.isArray(), Collections.<String>emptyList());
}
private static String fqName(Class<?> klass) {
@@ -74,14 +75,14 @@ public class TypeRef implements Comparable<TypeRef> {
}
public TypeRef(String fqName) {
- this(fqName, fqName, false, false, Collections.<TypeRef>emptyList());
+ this(fqName, fqName, false, false, Collections.<String>emptyList());
}
- public TypeRef(String fqName, String name, boolean array, List<TypeRef> args) {
+ public TypeRef(String fqName, String name, boolean array, List<String> args) {
this(fqName, name, false, array, args);
}
- public TypeRef args(List<TypeRef> args) {
+ public TypeRef args(List<String> args) {
return new TypeRef(fqName, plainName, primitive, array, args);
}
@@ -108,6 +109,14 @@ public class TypeRef implements Comparable<TypeRef> {
}
public TypeRef args(TypeRef... args) {
+ List<String> list = new ArrayList<>();
+ for (TypeRef arg : args) {
+ list.add(arg.plainName);
+ }
+ return args(list);
+ }
+
+ public TypeRef args(String... args) {
return args(asList(args));
}
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/persistence/EntityMirror.java b/container-compiler-plugin/src/main/java/io/trygvis/persistence/EntityMirror.java
index 5d6defb..140a910 100644
--- a/container-compiler-plugin/src/main/java/io/trygvis/persistence/EntityMirror.java
+++ b/container-compiler-plugin/src/main/java/io/trygvis/persistence/EntityMirror.java
@@ -3,6 +3,7 @@ package io.trygvis.persistence;
import io.trygvis.container.compiler.NotImplementedException;
import io.trygvis.container.compiler.SqlUnitModel;
import io.trygvis.container.compiler.model.ClassG;
+import io.trygvis.container.compiler.model.Constructor;
import io.trygvis.container.compiler.model.Imports;
import io.trygvis.container.compiler.model.MethodRef;
import io.trygvis.container.compiler.model.Parameters;
@@ -38,6 +39,8 @@ public class EntityMirror implements Comparable<EntityMirror> {
public final TypeRef type;
public final String tableName;
public final TypeRef daoType;
+ public final TypeRef utilsType;
+ public TypeRef idType;
public EntityMirror(GeneratorConfiguration generatorConfiguration, TypeRef type, String tableName) {
this.generatorConfiguration = generatorConfiguration;
@@ -45,6 +48,7 @@ public class EntityMirror implements Comparable<EntityMirror> {
this.tableName = tableName;
this.daoType = new TypeRef(type.plainName + "Dao").args(type.args);
+ this.utilsType = new TypeRef(type.plainName + ".Utils").args(type.args);
}
public void add(FieldMirror... fields) {
@@ -57,6 +61,10 @@ public class EntityMirror implements Comparable<EntityMirror> {
}
}
+ public void setIdType(TypeRef idType) {
+ this.idType = idType;
+ }
+
public FieldMirror getIdField() {
return idFields.get(0);
}
@@ -180,7 +188,7 @@ public class EntityMirror implements Comparable<EntityMirror> {
body.add(setter);
} else {
body.add(" " + field.type + " " + field.javaName + " = " + accessor + ";");
- body.add(" if(" + field.javaName + " == null) {");
+ body.add(" if (" + field.javaName + " == null) {");
body.add(" stmt.setNull(" + i + ", " + typesType + "." + typeHandler.typeName() + ");");
body.add(" } else {");
body.add(" " + setter);
@@ -189,7 +197,23 @@ public class EntityMirror implements Comparable<EntityMirror> {
}
body.add(" stmt.executeUpdate();");
body.add("}");
- return new MethodRef(PUBLIC | STATIC, TypeRef.VOID, "insertInto", p, body).exception(sqlExceptionType);
+ return new MethodRef(PUBLIC | STATIC, TypeRef.VOID, "insert" + type.className, p, body).exception(sqlExceptionType);
+ }
+
+ public MethodRef selectById(Imports imports) {
+ Parameters p = new Parameters();
+ p.addParameter(imports.add(Connection.class), "c");
+ p.addParameter(idType, "id");
+ return new MethodRef(PUBLIC | STATIC, type, "select" + type.className + "ById", p,
+ "throw new UnsupportedOperationException(\"Not implemented\");");
+ }
+
+ public MethodRef update(Imports imports) {
+ Parameters p = new Parameters();
+ p.addParameter(imports.add(Connection.class), "c");
+ p.addParameter(type, "entity");
+ return new MethodRef(PUBLIC | STATIC, type, "update" + type.className, p,
+ "throw new UnsupportedOperationException(\"Not implemented\");");
}
public MethodRef delete(Imports imports) {
@@ -208,11 +232,9 @@ public class EntityMirror implements Comparable<EntityMirror> {
arguments.add(o.name + "." + toGetterName(field.javaName) + "()");
}
}
- List<String> body = new ArrayList<>();
- body.add("deleteById(" + join(arguments, ", ") + ");");
- return new MethodRef(PUBLIC | STATIC, TypeRef.VOID, "delete", p, body).
- exception(imports.add(SQLException.class));
+ return new MethodRef(PUBLIC | STATIC, TypeRef.VOID, "delete" + type.className, p,
+ "delete" + type.className + "ById(" + join(arguments, ", ") + ");").exception(imports.add(SQLException.class));
}
public MethodRef deleteById(Imports imports) {
@@ -233,22 +255,29 @@ public class EntityMirror implements Comparable<EntityMirror> {
body.add(" stmt.executeUpdate();");
body.add("}");
- return new MethodRef(PUBLIC | STATIC, TypeRef.VOID, "deleteById", p, body).
+ return new MethodRef(PUBLIC | STATIC, TypeRef.VOID, "delete" + type.className + "ById", p, body).
exception(imports.add(SQLException.class));
}
public ClassG queryType(Imports imports) {
- TypeRef abstractQueryType = imports.add(AbstractTypedQuery.class).args(type);
+ TypeRef sqlQueryType = imports.add(AbstractTypedQuery.class).args(type);
TypeRef conType = imports.add(Connection.class);
TypeRef entityTypedQuery = new TypeRef(type.className + "TypedQuery");
+ TypeRef sqlExceptionType = new TypeRef(SQLException.class);
Parameters p = new Parameters();
ParameterRef c = p.addParameter(conType, "c");
-
- ClassG typedQuery = new ClassG(PUBLIC | STATIC, entityTypedQuery).
- extendsType(abstractQueryType);
- typedQuery.addConstructor(p, singletonList("super(" + c.name + ", " + daoType.className + ".desc);"));
- return typedQuery;
+ Constructor constructor = new Constructor(p, singletonList("super(" + c.name + ", " + daoType.className + ".desc);"));
+ ClassG g = new ClassG(PUBLIC | STATIC, entityTypedQuery).
+ extendsType(sqlQueryType).
+ add(constructor);
+ p = new Parameters();
+ ParameterRef rs = p.addParameter(new TypeRef(ResultSet.class), "rs");
+ MethodRef fromResultSet = new MethodRef(PUBLIC, type, "fromResultSet", p,
+ "return " + utilsType.className + ".fromResultSet" + type.className + "(" + rs.name + ");").
+ exception(sqlExceptionType);
+ g.add(fromResultSet);
+ return g;
}
public MethodRef query(Imports imports) {
@@ -258,10 +287,8 @@ public class EntityMirror implements Comparable<EntityMirror> {
Parameters p = new Parameters();
ParameterRef c = p.addParameter(conType, "c");
-
- List<String> body = new ArrayList<>();
- body.add("return new " + entityTypedQuery + "(" + c.name + ");");
- return new MethodRef(PUBLIC | STATIC, typedQueryType, "query", p, body);
+ return new MethodRef(PUBLIC | STATIC, typedQueryType, "query" + type.className, p,
+ "return new " + entityTypedQuery + "(" + c.name + ");");
}
public MethodRef fromResultSet(Imports g) {
@@ -313,7 +340,7 @@ public class EntityMirror implements Comparable<EntityMirror> {
body.add("return returnValue;");
- return new MethodRef(PUBLIC, type, "fromResultSet", p, body).
+ return new MethodRef(PUBLIC | STATIC, type, "fromResultSet" + type.className, p, body).
exception(g.add(SQLException.class));
}
@@ -336,4 +363,16 @@ public class EntityMirror implements Comparable<EntityMirror> {
public int compareTo(@SuppressWarnings("NullableProblems") EntityMirror o) {
return type.compareTo(o.type);
}
+
+ public ClassG utils(SqlUnitModel unit) {
+ ClassG g = new ClassG(PUBLIC | STATIC, utilsType);
+ g.add(insertInto(unit, g.imports));
+ g.add(selectById(g.imports));
+ g.add(update(g.imports));
+ g.add(delete(g.imports));
+ g.add(deleteById(g.imports));
+ g.add(query(g.imports));
+ g.add(fromResultSet(g.imports));
+ return g;
+ }
}
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/persistence/SqlEntitySet.java b/container-compiler-plugin/src/main/java/io/trygvis/persistence/SqlEntitySet.java
index 69176f8..455e3cd 100644
--- a/container-compiler-plugin/src/main/java/io/trygvis/persistence/SqlEntitySet.java
+++ b/container-compiler-plugin/src/main/java/io/trygvis/persistence/SqlEntitySet.java
@@ -1,4 +1,5 @@
package io.trygvis.persistence;
public @interface SqlEntitySet {
+ String name();
}
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/EntityManagerFactoryGenerator.java b/container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/EntityManagerFactoryGenerator.java
new file mode 100644
index 0000000..82e69e3
--- /dev/null
+++ b/container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/EntityManagerFactoryGenerator.java
@@ -0,0 +1,69 @@
+package io.trygvis.persistence.generators;
+
+import io.trygvis.container.compiler.SqlUnitModel;
+import io.trygvis.container.compiler.model.ClassG;
+import io.trygvis.container.compiler.model.Constructor;
+import io.trygvis.container.compiler.model.FieldRef;
+import io.trygvis.container.compiler.model.Imports;
+import io.trygvis.container.compiler.model.MethodRef;
+import io.trygvis.container.compiler.model.Parameters;
+import io.trygvis.container.compiler.model.TypeRef;
+import io.trygvis.persistence.EntityMirror;
+import io.trygvis.persistence.sql.SqlEntityManagerFactory;
+import io.trygvis.persistence.sql.SqlEntityMeta;
+import io.trygvis.persistence.sql.SqlUnit;
+
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.sql.Connection;
+import java.util.ArrayList;
+import java.util.List;
+
+import static io.trygvis.container.compiler.Utils.toClassName;
+import static java.lang.reflect.Modifier.*;
+import static org.apache.commons.lang.StringUtils.join;
+
+public class EntityManagerFactoryGenerator {
+ public static ClassG generateEntityManagerFactory(SqlUnitModel unit) throws IOException {
+ String prefix = unit.getPackageName() + "." + toClassName(unit.getName());
+ TypeRef emfType = new TypeRef(prefix + "EntityManagerFactory");
+
+ ClassG g = new ClassG(PUBLIC, emfType).
+ extendsType(SqlEntityManagerFactory.class);
+
+ List<String> s = new ArrayList<>();
+ for (EntityMirror entity : unit.getEntities().values()) {
+ s.add(entity.daoType.plainName + ".desc");
+ }
+ TypeRef sqlEntityMetaArrayType = new TypeRef(SqlEntityMeta[].class);
+ FieldRef entities = g.addField(PUBLIC | STATIC, sqlEntityMetaArrayType, "entities").
+ value("new " + sqlEntityMetaArrayType + "{" + join(s, ", ") + "}");
+
+ g.add(constructor(entities, g.imports));
+ g.add(createEntityManager(unit, g.imports));
+ return g;
+ }
+
+ private static Constructor constructor(FieldRef entities, Imports imports) {
+ TypeRef dataSourceType = imports.add(DataSource.class);
+ TypeRef sqlUnitType = imports.add(SqlUnit.class);
+ Parameters p = new Parameters();
+ Parameters.ParameterRef ds = p.addParameter(dataSourceType, "ds");
+ ArrayList<String> body = new ArrayList<>();
+ body.add("super(new " + sqlUnitType.plainName + "(" + entities.name + "), " + ds.name + ");");
+ return new Constructor(p, body);
+ }
+
+ private static MethodRef createEntityManager(SqlUnitModel unit, Imports imports) {
+ String prefix = unit.getPackageName() + "." + toClassName(unit.getName());
+ TypeRef entityManagerType = imports.add(EntityManager.class);
+ TypeRef connectionType = imports.add(Connection.class);
+ TypeRef emType = imports.add(new TypeRef(prefix + "EntityManager"));
+
+ Parameters p = new Parameters();
+ Parameters.ParameterRef c = p.addParameter(connectionType, "c");
+ return new MethodRef(PROTECTED, entityManagerType, "createEntityManager", p,
+ "return new " + emType + "(this, " + c.name + ");");
+ }
+}
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/EntityManagerGenerator.java b/container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/EntityManagerGenerator.java
new file mode 100644
index 0000000..f524b1a
--- /dev/null
+++ b/container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/EntityManagerGenerator.java
@@ -0,0 +1,129 @@
+package io.trygvis.persistence.generators;
+
+import io.trygvis.container.compiler.SqlUnitModel;
+import io.trygvis.container.compiler.model.ClassG;
+import io.trygvis.container.compiler.model.Constructor;
+import io.trygvis.container.compiler.model.FieldRef;
+import io.trygvis.container.compiler.model.Imports;
+import io.trygvis.container.compiler.model.MethodRef;
+import io.trygvis.container.compiler.model.Parameters;
+import io.trygvis.container.compiler.model.TypeRef;
+import io.trygvis.persistence.EntityMirror;
+import io.trygvis.persistence.sql.SqlDao;
+import io.trygvis.persistence.sql.SqlEntityManager;
+import io.trygvis.persistence.sql.SqlEntityManagerFactory;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static io.trygvis.container.compiler.Utils.toClassName;
+import static io.trygvis.container.compiler.Utils.toFieldName;
+import static io.trygvis.container.compiler.model.Parameters.ParameterRef;
+import static java.lang.reflect.Modifier.PUBLIC;
+
+public class EntityManagerGenerator {
+ public static ClassG generateEntityManager(SqlUnitModel unit) throws IOException {
+ String prefix = unit.getPackageName() + "." + toClassName(unit.getName());
+ TypeRef emType = new TypeRef(prefix + "EntityManager");
+
+ ClassG g = new ClassG(PUBLIC, emType).
+ extendsType(SqlEntityManager.class);
+
+ Map<EntityMirror, FieldRef> daoFields = new HashMap<>();
+ for (EntityMirror entity : unit.getEntities().values()) {
+ daoFields.put(entity, g.addField(entity.daoType, toFieldName(entity.type.className)));
+ }
+
+ g.add(constructor(unit, g.imports, daoFields));
+ g.add(getSqlDao(unit, g.imports, daoFields));
+
+ return g;
+ }
+
+ private static Constructor constructor(SqlUnitModel unit, Imports imports, Map<EntityMirror, FieldRef> daoFields) {
+ Parameters p = new Parameters();
+ ParameterRef semf = p.addParameter(imports.add(SqlEntityManagerFactory.class), "emf");
+ ParameterRef c = p.addParameter(imports.add(Connection.class), "c");
+ List<String> body = new ArrayList<>();
+ body.add("super(" + semf.name + ", " + c.name + ");");
+
+ for (EntityMirror entity : unit.getEntities().values()) {
+ FieldRef f = daoFields.get(entity);
+ body.add("this." + f.name + " = new " + entity.daoType.plainName + "(" + c.name + ");");
+ }
+ return new Constructor(p, body);
+ }
+
+ public static MethodRef getSqlDao(SqlUnitModel unit, Imports imports, Map<EntityMirror, FieldRef> daoFields) {
+ TypeRef sqlDatoType = imports.add(new TypeRef(SqlDao.class)).args("Id", "T");
+ Parameters p = new Parameters();
+ TypeRef klassType = new TypeRef(Class.class).args("T");
+ ParameterRef klass = p.addParameter(klassType, "klass");
+ List<String> body = new ArrayList<>();
+ for (EntityMirror entity : unit.getEntities().values()) {
+ body.add("if (klass == " + entity.type.plainName + ".class) {");
+ body.add(" @SuppressWarnings(\"unchecked\")");
+ body.add(" SqlDao<Id, T> dao = (SqlDao<Id, T>) " + daoFields.get(entity).name + ";");
+ body.add(" return (SqlDao<Id, T>) dao;");
+ body.add("}");
+ }
+ body.add("throw new RuntimeException(\"Type is not a part of this persistence unit: \" + klass);");
+ return new MethodRef(PUBLIC, sqlDatoType, "getDao", p, body).typeArgs("Id", "T");
+ }
+
+ /*
+ public static ClassG sem(EntityMirror entity) {
+ ClassG g = new ClassG(PUBLIC, new TypeRef(entity.type.className + "SEM"));
+ g.implementsType(g.imports.add(new TypeRef(SqlDao.class)).args(entity.idType, entity.type));
+ return g.
+ add(semFind(entity, g.imports)).
+ add(semPersist(entity, g.imports)).
+ add(semMerge(entity, g.imports)).
+ add(semRemove(entity, g.imports));
+ }
+
+ public static MethodRef semFind(EntityMirror entity, Imports imports) {
+ TypeRef type = imports.add(entity.type);
+ TypeRef sqlException = imports.add(SQLException.class);
+ Parameters p = new Parameters();
+ ParameterRef primaryKey = p.addParameter(new TypeRef(Object.class), "primaryKey");
+ List<String> body = new ArrayList<>();
+ body.add("throw new " + sqlException.plainName + "(\"Not implemented\");");
+ return new MethodRef(PUBLIC, type, "find", p, tryCatchSqlException(imports, body));
+ }
+
+ public static MethodRef semPersist(EntityMirror entity, Imports imports) {
+ TypeRef type = imports.add(entity.type);
+ TypeRef dao = imports.add(entity.daoType);
+ Parameters p = new Parameters();
+ ParameterRef e = p.addParameter(type, "entity");
+ List<String> body = new ArrayList<>();
+ body.add(dao.plainName + ".insertInto(currentConnection(), " + e.name + ");");
+ return new MethodRef(PUBLIC, VOID, "persist", p, tryCatchSqlException(imports, body));
+ }
+
+ public static MethodRef semMerge(EntityMirror entity, Imports imports) {
+ TypeRef type = imports.add(entity.type);
+ TypeRef sqlException = imports.add(SQLException.class);
+ Parameters p = new Parameters();
+ ParameterRef e = p.addParameter(type, "entity");
+ List<String> body = new ArrayList<>();
+ body.add("throw new " + sqlException.plainName + "(\"Not implemented\");");
+ return new MethodRef(PUBLIC, type, "merge", p, tryCatchSqlException(imports, body));
+ }
+
+ public static MethodRef semRemove(EntityMirror entity, Imports imports) {
+ TypeRef type = imports.add(entity.type);
+ TypeRef sqlException = imports.add(SQLException.class);
+ Parameters p = new Parameters();
+ ParameterRef e = p.addParameter(type, "entity");
+ List<String> body = new ArrayList<>();
+ body.add("throw new " + sqlException.plainName + "(\"Not implemented\");");
+ return new MethodRef(PUBLIC, VOID, "remove", p, tryCatchSqlException(imports, body));
+ }
+ */
+}
diff --git a/container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/SequencesGenerator.java b/container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/SequencesGenerator.java
new file mode 100644
index 0000000..07823af
--- /dev/null
+++ b/container-compiler-plugin/src/main/java/io/trygvis/persistence/generators/SequencesGenerator.java
@@ -0,0 +1,41 @@
+package io.trygvis.persistence.generators;
+
+import io.trygvis.container.compiler.SqlUnitModel;
+import io.trygvis.container.compiler.model.ClassG;
+import io.trygvis.container.compiler.model.FieldRef;
+import io.trygvis.container.compiler.model.TypeRef;
+import io.trygvis.persistence.SequenceMirror;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static io.trygvis.container.compiler.Utils.toJavaString;
+import static java.lang.reflect.Modifier.PUBLIC;
+import static org.apache.commons.lang.StringUtils.capitalize;
+import static org.apache.commons.lang.StringUtils.join;
+
+public class SequencesGenerator {
+ public static ClassG generateSequences(SqlUnitModel unit) {
+ TypeRef sequences = new TypeRef(unit.getPackageName() + ".Sequences");
+ ClassG g = new ClassG(PUBLIC, sequences);
+ List<String> creates = new ArrayList<>();
+ List<String> drops = new ArrayList<>();
+ for (SequenceMirror sequence : unit.getSequences().values()) {
+ TypeRef stringType = g.imports.add(String.class);
+ String value = "CREATE SEQUENCE " + sequence.sequenceName + ";";
+ FieldRef f = g.addPublicStaticFinalField(stringType, "create" + capitalize(sequence.name)).
+ value(toJavaString(value));
+ creates.add(f.name);
+
+ f = g.addPublicStaticFinalField(stringType, "drop" + capitalize(sequence.name)).
+ value(toJavaString("DROP SEQUENCE " + sequence.sequenceName) + ";");
+ drops.add(f.name);
+
+ }
+ g.addPublicStaticFinalField(new TypeRef(String[].class), "createSequences").
+ value("new String[]{" + join(creates, ", ") + "}");
+ g.addPublicStaticFinalField(new TypeRef(String[].class), "dropSequences").
+ value("new String[]{" + join(drops, ", ") + "}");
+ return g;
+ }
+}
diff --git a/container-compiler-plugin/src/test/java/io/trygvis/container/compiler/InMemoryJavaFileManager.java b/container-compiler-plugin/src/test/java/io/trygvis/container/compiler/InMemoryJavaFileManager.java
index 60a78ae..5778adf 100644
--- a/container-compiler-plugin/src/test/java/io/trygvis/container/compiler/InMemoryJavaFileManager.java
+++ b/container-compiler-plugin/src/test/java/io/trygvis/container/compiler/InMemoryJavaFileManager.java
@@ -61,7 +61,7 @@ public class InMemoryJavaFileManager extends ForwardingJavaFileManager<StandardJ
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
- if(code == null) {
+ if (code == null) {
throw new FileNotFoundException(className);
}
diff --git a/container-compiler-plugin/src/test/java/io/trygvis/container/compiler/ProcessorTest.java b/container-compiler-plugin/src/test/java/io/trygvis/container/compiler/ProcessorTest.java
index 4266a01..ed894c5 100644
--- a/container-compiler-plugin/src/test/java/io/trygvis/container/compiler/ProcessorTest.java
+++ b/container-compiler-plugin/src/test/java/io/trygvis/container/compiler/ProcessorTest.java
@@ -62,7 +62,8 @@ public class ProcessorTest {
assertThat(fileManager.codes.keySet()).containsOnly(
"io.trygvis.persistence.test.Sequences",
- "io.trygvis.persistence.test.Session",
+ "io.trygvis.persistence.test.TestEntityManager",
+ "io.trygvis.persistence.test.TestEntityManagerFactory",
"io.trygvis.persistence.test.PersonDao",
"io.trygvis.persistence.test.ChildEntityDao");
assertThat(collector.getDiagnostics()).isEmpty();
diff --git a/container-compiler-plugin/src/test/java/io/trygvis/persistence/EntityMirrorTest.java b/container-compiler-plugin/src/test/java/io/trygvis/persistence/EntityMirrorTest.java
index 85e3741..590b8d0 100644
--- a/container-compiler-plugin/src/test/java/io/trygvis/persistence/EntityMirrorTest.java
+++ b/container-compiler-plugin/src/test/java/io/trygvis/persistence/EntityMirrorTest.java
@@ -10,6 +10,7 @@ import org.testng.annotations.Test;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
+import static io.trygvis.container.compiler.Utils.EOL;
import static io.trygvis.persistence.FieldMirror.AccessorType.FIELD;
import static io.trygvis.persistence.FieldMirror.AccessorType.METHOD;
import static io.trygvis.persistence.FieldMirror.FieldType.PRIMITIVE;
@@ -84,7 +85,7 @@ public class EntityMirrorTest {
new Object[]{new FieldMirror[]{name}, join(
"try(java.sql.PreparedStatement stmt = con.prepareStatement(insertIntoSql)) {",
" java.lang.String name = o.name;",
- " if(name == null) {",
+ " if (name == null) {",
" stmt.setNull(1, java.sql.Types.VARCHAR);",
" } else {",
" stmt.setString(1, o.name);",
@@ -218,7 +219,7 @@ public class EntityMirrorTest {
private static String join(String... strings) {
StringBuilder buffer = new StringBuilder();
for (String string : strings) {
- buffer.append(string).append("\n");
+ buffer.append(string).append(EOL);
}
return buffer.toString();
}
diff --git a/container-compiler-plugin/src/test/resources/io/trygvis/persistence/test/package-info.java b/container-compiler-plugin/src/test/resources/io/trygvis/persistence/test/package-info.java
index b211cd3..5a80282 100644
--- a/container-compiler-plugin/src/test/resources/io/trygvis/persistence/test/package-info.java
+++ b/container-compiler-plugin/src/test/resources/io/trygvis/persistence/test/package-info.java
@@ -1,4 +1,4 @@
-@SqlEntitySet
+@SqlEntitySet(name = "Test")
package io.trygvis.persistence.test;
import io.trygvis.persistence.SqlEntitySet;
diff --git a/myapp/src/main/java/io/trygvis/container/myapp/AddressBook.java b/myapp/src/main/java/io/trygvis/container/myapp/AddressBookDirect.java
index d5c93fe..df0f283 100644
--- a/myapp/src/main/java/io/trygvis/container/myapp/AddressBook.java
+++ b/myapp/src/main/java/io/trygvis/container/myapp/AddressBookDirect.java
@@ -10,10 +10,11 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
+import static io.trygvis.container.myapp.CompanyDao.Utils.insertCompany;
import static io.trygvis.container.myapp.Contact.Gender.FEMALE;
import static io.trygvis.container.myapp.Contact.Gender.MALE;
-public class AddressBook {
+public class AddressBookDirect {
private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
@@ -21,7 +22,7 @@ public class AddressBook {
public static void main(String[] args) throws Exception {
try {
- new AddressBook().work();
+ new AddressBookDirect().work();
} catch (EOFException ignore) {
}
}
@@ -142,14 +143,14 @@ public class AddressBook {
String name = line();
Company company = new Company(name);
- CompanyDao.insertInto(c, company);
+ insertCompany(c, company);
}
public void deleteCompany() {
}
public void listCompanies() {
- TypedQuery<Company> p = CompanyDao.query(c);
+ TypedQuery<Company> p = CompanyDao.Utils.queryCompany(c);
List<Company> resultList = p.getResultList();
for (Company company : resultList) {
@@ -214,7 +215,7 @@ public class AddressBook {
Company company = null;
Contact o = new Contact(name, g, company);
- ContactDao.insertInto(c, o);
+ ContactDao.Utils.insertContact(c, o);
}
public void deleteContact() {
@@ -222,7 +223,7 @@ public class AddressBook {
}
public void listContacts() {
- TypedQuery<Contact> p = ContactDao.query(c);
+ TypedQuery<Contact> p = ContactDao.Utils.queryContact(c);
List<Contact> resultList = p.getResultList();
for (Contact contact : resultList) {
diff --git a/myapp/src/main/java/io/trygvis/container/myapp/AddressBookJpa.java b/myapp/src/main/java/io/trygvis/container/myapp/AddressBookJpa.java
new file mode 100644
index 0000000..3981805
--- /dev/null
+++ b/myapp/src/main/java/io/trygvis/container/myapp/AddressBookJpa.java
@@ -0,0 +1,242 @@
+package io.trygvis.container.myapp;
+
+import io.trygvis.persistence.sql.SqlEntityMeta;
+import io.trygvis.persistence.sql.SqlUnit;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.persistence.TypedQuery;
+import javax.sql.DataSource;
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.InputStreamReader;
+import java.sql.SQLException;
+import java.util.List;
+
+import static io.trygvis.container.myapp.Contact.Gender.FEMALE;
+import static io.trygvis.container.myapp.Contact.Gender.MALE;
+
+public class AddressBookJpa {
+
+ private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+
+ private EntityManager entityManager;
+
+ public static void main(String[] args) throws Exception {
+ try {
+ new AddressBookJpa().work();
+ } catch (EOFException ignore) {
+ }
+ }
+
+ private void work() throws Exception {
+ DataSource ds = new DriverManagerDataSource("jdbc:h2:mem:address-book;DB_CLOSE_DELAY=-1");
+ EntityManagerFactory emf = new MyAppEntityManagerFactory(ds);
+ boolean done = false;
+ while (!done) {
+ try {
+ entityManager = emf.createEntityManager();
+ EntityTransaction transaction = entityManager.getTransaction();
+ done = main();
+ System.out.println("OK");
+ transaction.commit();
+ } finally {
+ entityManager.close();
+ }
+ }
+ }
+
+ private boolean main() throws Exception {
+ System.out.println("Menu:");
+ System.out.println("c Create");
+ System.out.println("d Drop");
+ System.out.println("m Company menu");
+ System.out.println("n Contact menu");
+ System.out.println("q Quit");
+ String cmd = cmd();
+ switch (cmd) {
+ case "c":
+ create();
+ break;
+ case "d":
+ drop();
+ break;
+ case "m":
+ company();
+ break;
+ case "n":
+ contact();
+ break;
+ case "q":
+ return true;
+ default:
+ System.out.println("Unknown command: " + cmd);
+ }
+ return false;
+ }
+
+ private String cmd() throws Exception {
+ String cmd = null;
+ do {
+ String read = line();
+ if (read.length() != 0) {
+ cmd = read.trim();
+ }
+ } while (cmd == null);
+ return cmd;
+ }
+
+ private String line() throws Exception {
+ String line = reader.readLine();
+ if (line == null) {
+ throw new EOFException();
+ }
+ return line.trim();
+ }
+
+ public void create() throws SQLException {
+ for (SqlEntityMeta entityMeta : entityManager.unwrap(SqlUnit.class).getEntities()) {
+ entityManager.createNativeQuery(entityMeta.createTableSql).executeUpdate();
+ }
+ for (String sql : Sequences.createSequences) {
+ entityManager.createNativeQuery(sql).executeUpdate();
+ }
+ }
+
+ public void drop() throws SQLException {
+ for (String sql : Sequences.dropSequences) {
+ entityManager.createNativeQuery(sql);
+ }
+ for (SqlEntityMeta entityMeta : entityManager.unwrap(SqlUnit.class).getEntities()) {
+ entityManager.createNativeQuery(entityMeta.dropTableSql).executeUpdate();
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ // Company
+ // -----------------------------------------------------------------------
+
+ private void company() throws Exception {
+ while (true) {
+ System.out.println("Company menu:");
+ System.out.println("c Create");
+ System.out.println("d Drop");
+ System.out.println("d List");
+ System.out.println("q Back");
+ String cmd = cmd();
+ switch (cmd) {
+ case "c":
+ addCompany();
+ break;
+ case "d":
+ deleteCompany();
+ break;
+ case "l":
+ listCompanies();
+ break;
+ case "q":
+ return;
+ default:
+ System.out.println("Unknown command: " + cmd);
+ }
+ }
+ }
+
+ public void addCompany() throws Exception {
+ System.out.print("Name: ");
+ String name = line();
+
+ Company company = new Company(name);
+ entityManager.persist(company);
+ }
+
+ public void deleteCompany() {
+ }
+
+ public void listCompanies() {
+ TypedQuery<Company> p = entityManager.createQuery("", Company.class);
+
+ List<Company> resultList = p.getResultList();
+ for (Company company : resultList) {
+ System.out.println("=====================");
+ System.out.println("Id: " + company.getId());
+ System.out.println("Name: " + company.name);
+ }
+ System.out.println();
+ }
+
+ // -----------------------------------------------------------------------
+ // Contact
+ // -----------------------------------------------------------------------
+
+ private void contact() throws Exception {
+ while (true) {
+ System.out.println("Contact menu:");
+ System.out.println("c Create");
+ System.out.println("d Drop");
+ System.out.println("q Back");
+ String cmd = cmd();
+ switch (cmd) {
+ case "c":
+ addContact();
+ break;
+ case "d":
+ deleteContact();
+ break;
+ case "l":
+ listContacts();
+ break;
+ case "q":
+ return;
+ default:
+ System.out.println("Unknown command: " + cmd);
+ }
+ }
+ }
+
+ public void addContact() throws Exception {
+ System.out.print("Name: ");
+ String name = line();
+ Contact.Gender g = null;
+ while (g == null) {
+ System.out.print("Gender (m/f): ");
+ try {
+ String line = line();
+ switch (line) {
+ case "m":
+ g = MALE;
+ break;
+ case "f":
+ g = FEMALE;
+ break;
+ default:
+ g = Contact.Gender.valueOf(line);
+ break;
+ }
+ } catch (IllegalArgumentException ignore) {
+ }
+ }
+
+ Company company = null;
+ Contact o = new Contact(name, g, company);
+ entityManager.persist(company);
+ }
+
+ public void deleteContact() {
+
+ }
+
+ public void listContacts() {
+ TypedQuery<Contact> p = entityManager.createQuery("", Contact.class);
+
+ List<Contact> resultList = p.getResultList();
+ for (Contact contact : resultList) {
+ System.out.println("=====================");
+ System.out.println("Id: " + contact.getId());
+ System.out.println("Name: " + contact.name);
+ System.out.println("Gender: " + contact.gender);
+ }
+ System.out.println();
+ }
+}
diff --git a/myapp/src/main/java/io/trygvis/container/myapp/DriverManagerDataSource.java b/myapp/src/main/java/io/trygvis/container/myapp/DriverManagerDataSource.java
new file mode 100644
index 0000000..3165f17
--- /dev/null
+++ b/myapp/src/main/java/io/trygvis/container/myapp/DriverManagerDataSource.java
@@ -0,0 +1,60 @@
+package io.trygvis.container.myapp;
+
+import javax.sql.DataSource;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.logging.Logger;
+
+class DriverManagerDataSource implements DataSource {
+ private final String url;
+
+ DriverManagerDataSource(String url) {
+ this.url = url;
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ return DriverManager.getConnection(url);
+ }
+
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException {
+ return DriverManager.getConnection(url, username, password);
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException {
+ return null;
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter out) throws SQLException {
+ }
+
+ @Override
+ public void setLoginTimeout(int seconds) throws SQLException {
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ return 0;
+ }
+
+ @Override
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ return null;
+ }
+
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException {
+ return null;
+ }
+
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException {
+ return false;
+ }
+}
diff --git a/myapp/src/main/java/io/trygvis/container/myapp/package-info.java b/myapp/src/main/java/io/trygvis/container/myapp/package-info.java
index c212ca1..fdb7353 100644
--- a/myapp/src/main/java/io/trygvis/container/myapp/package-info.java
+++ b/myapp/src/main/java/io/trygvis/container/myapp/package-info.java
@@ -1,3 +1,3 @@
-@SqlEntitySet package io.trygvis.container.myapp;
+@SqlEntitySet(name = "MyApp") package io.trygvis.container.myapp;
import io.trygvis.persistence.SqlEntitySet;
diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/AbstractTypedQuery.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/AbstractTypedQuery.java
index 1cb8405..ab92994 100644
--- a/sql-persistence/src/main/java/io/trygvis/persistence/sql/AbstractTypedQuery.java
+++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/AbstractTypedQuery.java
@@ -19,20 +19,18 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-public abstract class AbstractTypedQuery<A> implements TypedQuery<A> {
+public abstract class AbstractTypedQuery<A> implements TypedQuery<A> , FromResultSet<A> {
private final Connection c;
- private final SqlEntityDesc sqlEntity;
+ private final SqlEntityMeta sqlEntity;
private int firstResult;
private int maxResults;
- protected AbstractTypedQuery(Connection c, SqlEntityDesc sqlEntity) {
+ protected AbstractTypedQuery(Connection c, SqlEntityMeta sqlEntity) {
this.c = c;
this.sqlEntity = sqlEntity;
}
- public abstract A fromResultSet(ResultSet rs) throws SQLException;
-
@Override
public TypedQuery<A> setMaxResults(int maxResult) {
this.maxResults = maxResult;
@@ -192,10 +190,10 @@ public abstract class AbstractTypedQuery<A> implements TypedQuery<A> {
public List<A> getResultList(Integer offset, Integer limit) {
String sql = "SELECT " + sqlEntity.defaultFields + " FROM " + sqlEntity.tableName;
- if(offset != null) {
+ if (offset != null) {
sql += " OFFSET " + offset;
}
- if(limit != null) {
+ if (limit != null) {
sql += " LIMIT " + limit;
}
diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/FromResultSet.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/FromResultSet.java
new file mode 100644
index 0000000..ec25f50
--- /dev/null
+++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/FromResultSet.java
@@ -0,0 +1,8 @@
+package io.trygvis.persistence.sql;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public interface FromResultSet<T> {
+ T fromResultSet(ResultSet rs) throws SQLException;
+}
diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlDao.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlDao.java
new file mode 100644
index 0000000..7df3658
--- /dev/null
+++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlDao.java
@@ -0,0 +1,25 @@
+package io.trygvis.persistence.sql;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+public abstract class SqlDao<Id, T> implements FromResultSet<T> {
+
+ protected final Connection c;
+
+ protected SqlDao(Connection c) {
+ this.c = c;
+ }
+
+ public abstract void insert(T o) throws SQLException;
+
+ public abstract void delete(T o) throws SQLException;
+
+ public abstract void deleteById(Id id) throws SQLException;
+
+// public abstract TypedQuery<T> query();
+
+ public abstract T selectById(Id id) throws SQLException;
+
+ public abstract void update(T entity) throws SQLException;
+}
diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityDesc.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityDesc.java
deleted file mode 100644
index dbbeed7..0000000
--- a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityDesc.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.trygvis.persistence.sql;
-
-public class SqlEntityDesc {
- public final String tableName;
- public final String defaultFields;
-
- public SqlEntityDesc(String tableName, String defaultFields) {
- this.tableName = tableName;
- this.defaultFields = defaultFields;
- }
-}
diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManager.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManager.java
new file mode 100644
index 0000000..adcd2e2
--- /dev/null
+++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManager.java
@@ -0,0 +1,419 @@
+package io.trygvis.persistence.sql;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+import javax.persistence.FlushModeType;
+import javax.persistence.LockModeType;
+import javax.persistence.PersistenceException;
+import javax.persistence.Query;
+import javax.persistence.RollbackException;
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.metamodel.Metamodel;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static javax.persistence.FlushModeType.AUTO;
+import static javax.persistence.LockModeType.NONE;
+
+public abstract class SqlEntityManager implements EntityManager {
+
+ private Map<String, Object> properties = new HashMap<>();
+
+ public abstract <Id, T> SqlDao<Id, T> getDao(Class<T> klass);
+
+ // -----------------------------------------------------------------------
+ //
+ // -----------------------------------------------------------------------
+
+ private final SqlEntityManagerFactory factory;
+
+ private final Connection c;
+
+ private boolean autoCommit;
+
+ private final Tx tx = new Tx();
+
+ protected Connection currentConnection() {
+ return c;
+ }
+
+ // -----------------------------------------------------------------------
+ // EntityManager Implementation
+ // -----------------------------------------------------------------------
+
+ protected SqlEntityManager(SqlEntityManagerFactory factory, Connection c) {
+ this.factory = factory;
+ this.c = c;
+
+ try {
+ autoCommit = c.getAutoCommit();
+ } catch (SQLException e) {
+ throw new PersistenceException(e);
+ }
+ }
+
+ @Override
+ public void persist(Object entity) {
+ if (entity == null) {
+ throw new NullPointerException("entity");
+ }
+
+ try {
+ @SuppressWarnings("unchecked")
+ Class<Object> klass = (Class<Object>) entity.getClass();
+ this.<Object, Object>getDao(klass).insert(entity);
+ } catch (SQLException e) {
+ throw new PersistenceException(e);
+ }
+ }
+
+ @Override
+ public <T> T merge(T entity) {
+ if (entity == null) {
+ throw new NullPointerException("entity");
+ }
+
+ try {
+ @SuppressWarnings("unchecked")
+ Class<T> klass = (Class<T>) entity.getClass();
+ SqlDao<Object, T> dao = getDao(klass);
+ T t = dao.selectById(entity);
+ if (t == null) {
+ dao.insert(entity);
+ } else {
+ dao.update(entity);
+ }
+
+ return t;
+ } catch (SQLException e) {
+ throw new PersistenceException(e);
+ }
+ }
+
+ @Override
+ public void remove(Object entity) {
+ if (entity == null) {
+ throw new NullPointerException("entity");
+ }
+
+ try {
+ @SuppressWarnings("unchecked")
+ Class<Object> klass = (Class<Object>) entity.getClass();
+ this.<Object, Object>getDao(klass).delete(entity);
+ } catch (SQLException e) {
+ throw new PersistenceException(e);
+ }
+ }
+
+ @Override
+ public <T> T find(Class<T> entityClass, Object primaryKey) {
+ try {
+ SqlDao<Object, T> dao = getDao(entityClass);
+ return dao.selectById(primaryKey);
+ } catch (SQLException e) {
+ throw new PersistenceException(e);
+ }
+ }
+
+ @Override
+ public <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties) {
+ return find(entityClass, primaryKey);
+ }
+
+ @Override
+ public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode) {
+ if (lockMode != NONE) {
+ throw new IllegalArgumentException("Only lockMode = NONE is supported.");
+ }
+ return find(entityClass, primaryKey);
+ }
+
+ @Override
+ public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String, Object> properties) {
+ return find(entityClass, primaryKey, lockMode);
+ }
+
+ @Override
+ public <T> T getReference(Class<T> entityClass, Object primaryKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void setFlushMode(FlushModeType flushMode) {
+ if (flushMode != AUTO) {
+ throw new RuntimeException("Flush mode not supported: " + flushMode);
+ }
+ }
+
+ @Override
+ public FlushModeType getFlushMode() {
+ return AUTO;
+ }
+
+ @Override
+ public void lock(Object entity, LockModeType lockMode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void lock(Object entity, LockModeType lockMode, Map<String, Object> properties) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void refresh(Object entity) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void refresh(Object entity, Map<String, Object> properties) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void refresh(Object entity, LockModeType lockMode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void refresh(Object entity, LockModeType lockMode, Map<String, Object> properties) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ }
+
+ @Override
+ public void detach(Object entity) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean contains(Object entity) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public LockModeType getLockMode(Object entity) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setProperty(String propertyName, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Map<String, Object> getProperties() {
+ return properties;
+ }
+
+ @Override
+ public Query createQuery(String qlString) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Query createNamedQuery(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Query createNativeQuery(String sql) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Query createNativeQuery(String sqlString, Class resultClass) {
+ // What happens if the transaction is aborted and this query is executes? Can a Query outlive it's connection?
+ // Or even EntityManager? Should probably store a reference to the current connection and check that the
+ // current one is the same when executing.
+ @SuppressWarnings({"UnnecessaryLocalVariable", "unchecked"})
+ Class<Object> klass = resultClass;
+
+ SqlDao<?, Object> dao = this.<Object, Object>getDao(klass);
+ return new SqlQuery<>(dao, new SqlExecutorDelegate(), sqlString, true);
+ }
+
+ private class SqlExecutorDelegate implements SqlExecutor {
+
+ // For now an EntityManager wraps a Connection
+ private Connection getCurrentOrNewConnection() {
+ return c;
+ }
+
+ @Override
+ public int executeUpdate(UpdateCommand command) {
+ if (!isOpen()) {
+ throw new PersistenceException("This entity manager is closed.");
+ }
+
+ Connection c = getCurrentOrNewConnection();
+
+ try {
+ return command.run(c);
+ } catch (SQLException e) {
+ throw new PersistenceException(e);
+ }
+ }
+
+ @Override
+ public <T> List<T> executeQuery(QueryCommand<T> command) {
+ if (!isOpen()) {
+ throw new PersistenceException("This entity manager is closed.");
+ }
+
+ Connection c = getCurrentOrNewConnection();
+
+ try {
+ return command.run(c);
+ } catch (SQLException e) {
+ throw new PersistenceException(e);
+ }
+ }
+ }
+
+ @Override
+ public Query createNativeQuery(String sqlString, String resultSetMapping) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void joinTransaction() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T unwrap(Class<T> cls) {
+ if(cls == SqlUnit.class) {
+ return cls.cast(this.factory.sqlUnit);
+ }
+ throw new PersistenceException();
+ }
+
+ @Override
+ public Object getDelegate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public boolean isOpen() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public EntityTransaction getTransaction() {
+ return tx;
+ }
+
+ @Override
+ public SqlEntityManagerFactory getEntityManagerFactory() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public CriteriaBuilder getCriteriaBuilder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Metamodel getMetamodel() {
+ throw new UnsupportedOperationException();
+ }
+
+ private class Tx implements EntityTransaction {
+ private boolean active;
+ private boolean rollbackOnly;
+
+ @Override
+ public void begin() {
+ try {
+ if (autoCommit) {
+ c.setAutoCommit(false);
+ autoCommit = false;
+ }
+ active = true;
+ rollbackOnly = false;
+ } catch (SQLException e) {
+ throw new PersistenceException(e);
+ }
+ }
+
+ @Override
+ public void commit() {
+ if (rollbackOnly) {
+ throw new RollbackException("Transaction marked for rollback only.");
+ }
+
+ if (autoCommit) {
+ return;
+ }
+
+ try {
+ c.commit();
+ } catch (SQLException e) {
+ throw new PersistenceException(e);
+ }
+
+ active = true;
+ }
+
+ @Override
+ public void rollback() {
+ active = false;
+ if (autoCommit) {
+ return;
+ }
+
+ try {
+ c.rollback();
+ } catch (SQLException e) {
+ throw new PersistenceException(e);
+ }
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ this.rollbackOnly = active;
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return rollbackOnly;
+ }
+
+ @Override
+ public boolean isActive() {
+ return active;
+ }
+ }
+}
diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManagerFactory.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManagerFactory.java
new file mode 100644
index 0000000..d48f7e5
--- /dev/null
+++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManagerFactory.java
@@ -0,0 +1,105 @@
+package io.trygvis.persistence.sql;
+
+import javax.persistence.Cache;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceException;
+import javax.persistence.PersistenceUnitUtil;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.metamodel.Metamodel;
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Map;
+
+import static java.util.Collections.emptyMap;
+
+public abstract class SqlEntityManagerFactory implements EntityManagerFactory {
+
+ public final SqlUnit sqlUnit;
+
+ private final DataSource dataSource;
+
+ protected SqlEntityManagerFactory(SqlUnit sqlUnit, DataSource dataSource) {
+ this.sqlUnit = sqlUnit;
+ this.dataSource = dataSource;
+ }
+
+ // -----------------------------------------------------------------------
+ //
+ // -----------------------------------------------------------------------
+
+ protected abstract EntityManager createEntityManager(Connection c);
+
+ // -----------------------------------------------------------------------
+ //
+ // -----------------------------------------------------------------------
+
+ @Override
+ public EntityManager createEntityManager() {
+ return createEntityManager(emptyMap());
+ }
+
+ @Override
+ public EntityManager createEntityManager(Map map) {
+ try {
+ Connection c = dataSource.getConnection();
+ return createEntityManager(c);
+ } catch (SQLException e) {
+ throw new PersistenceException("Could not get connection", e);
+ }
+ }
+
+ @Override
+ public CriteriaBuilder getCriteriaBuilder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Metamodel getMetamodel() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return true;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public Map<String, Object> getProperties() {
+ return emptyMap();
+ }
+
+ @Override
+ public Cache getCache() {
+ return new NoOpCache();
+ }
+
+ @Override
+ public PersistenceUnitUtil getPersistenceUnitUtil() {
+ throw new UnsupportedOperationException();
+ }
+
+ private static class NoOpCache implements Cache {
+ @Override
+ public boolean contains(Class cls, Object primaryKey) {
+ return false;
+ }
+
+ @Override
+ public void evict(Class cls, Object primaryKey) {
+ }
+
+ @Override
+ public void evict(Class cls) {
+ }
+
+ @Override
+ public void evictAll() {
+ }
+ }
+}
diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityMeta.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityMeta.java
new file mode 100644
index 0000000..c8cbcac
--- /dev/null
+++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityMeta.java
@@ -0,0 +1,15 @@
+package io.trygvis.persistence.sql;
+
+public class SqlEntityMeta {
+ public final String tableName;
+ public final String defaultFields;
+ public final String createTableSql;
+ public final String dropTableSql;
+
+ public SqlEntityMeta(String tableName, String defaultFields, String createTableSql, String dropTableSql) {
+ this.tableName = tableName;
+ this.defaultFields = defaultFields;
+ this.createTableSql = createTableSql;
+ this.dropTableSql = dropTableSql;
+ }
+}
diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlExecutor.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlExecutor.java
new file mode 100644
index 0000000..ad6b8af
--- /dev/null
+++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlExecutor.java
@@ -0,0 +1,19 @@
+package io.trygvis.persistence.sql;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+
+public interface SqlExecutor {
+ int executeUpdate(UpdateCommand command);
+
+ <T> List<T> executeQuery(QueryCommand<T> command);
+
+ static interface UpdateCommand {
+ int run(Connection c) throws SQLException;
+ }
+
+ static interface QueryCommand<T> {
+ List<T> run(Connection c) throws SQLException;
+ }
+}
diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlQuery.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlQuery.java
new file mode 100644
index 0000000..b50b56e
--- /dev/null
+++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlQuery.java
@@ -0,0 +1,249 @@
+package io.trygvis.persistence.sql;
+
+import javax.persistence.FlushModeType;
+import javax.persistence.LockModeType;
+import javax.persistence.NoResultException;
+import javax.persistence.NonUniqueResultException;
+import javax.persistence.Parameter;
+import javax.persistence.PersistenceException;
+import javax.persistence.TemporalType;
+import javax.persistence.TypedQuery;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static java.util.Collections.emptyMap;
+
+public class SqlQuery<T> implements TypedQuery<T> {
+ private final FromResultSet<T> fromResultSet;
+ private final SqlExecutor executor;
+ private final String sql;
+
+ private final boolean isSqlFinal;
+ private int maxResults = -1;
+ private int firstResult = -1;
+
+ public SqlQuery(FromResultSet<T> fromResultSet, SqlExecutor executor, String sql, boolean sqlFinal) {
+ this.fromResultSet = fromResultSet;
+ this.executor = executor;
+ this.sql = sql;
+ this.isSqlFinal = sqlFinal;
+ }
+
+ @Override
+ public List<T> getResultList() {
+ return getResultList(firstResult, maxResults);
+ }
+
+ @Override
+ public T getSingleResult() {
+ List<T> list = getResultList(0, 2);
+ if (list.size() == 1) {
+ return list.get(0);
+ }
+
+ if (list.size() == 0) {
+ throw new NoResultException();
+ }
+
+ throw new NonUniqueResultException();
+ }
+
+ public List<T> getResultList(int offset, int limit) {
+ final String sql = generateSql(this.sql, offset, limit);
+ return executor.executeQuery(new SqlExecutor.QueryCommand<T>() {
+ @Override
+ public List<T> run(Connection c) throws SQLException {
+ List<T> list = new ArrayList<>();
+ try (Statement stmt = c.createStatement()) {
+ ResultSet rs = stmt.executeQuery(sql);
+ while (rs.next()) {
+ T t = fromResultSet.fromResultSet(rs);
+ list.add(t);
+ }
+ }
+ return list;
+ }
+ });
+ }
+
+ private static String generateSql(String sql, int offset, int limit) {
+ if (offset > 0) {
+ sql += " OFFSET " + offset;
+ }
+ if (limit > 0) {
+ sql += " LIMIT " + limit;
+ }
+
+ return sql;
+ }
+
+ @Override
+ public int executeUpdate() {
+ return executor.executeUpdate(new SqlExecutor.UpdateCommand() {
+ @Override
+ public int run(Connection c) throws SQLException {
+ try (Statement stmt = c.createStatement()) {
+ return stmt.executeUpdate(sql);
+ }
+ }
+ });
+ }
+
+ @Override
+ public SqlQuery<T> setMaxResults(int maxResult) {
+ if (maxResult <= 0) {
+ throw new IllegalArgumentException("maxResult has to be positive: " + maxResult);
+ }
+ this.maxResults = maxResult;
+ return this;
+ }
+
+ @Override
+ public int getMaxResults() {
+ return maxResults;
+ }
+
+ @Override
+ public SqlQuery<T> setFirstResult(int startPosition) {
+ this.firstResult = startPosition;
+ return this;
+ }
+
+ @Override
+ public int getFirstResult() {
+ return firstResult;
+ }
+
+ @Override
+ public SqlQuery<T> setHint(String hintName, Object value) {
+ return null;
+ }
+
+ @Override
+ public Map<String, Object> getHints() {
+ return emptyMap();
+ }
+
+ @Override
+ public <A> SqlQuery<T> setParameter(Parameter<A> param, A value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SqlQuery<T> setParameter(Parameter<Calendar> param, Calendar value, TemporalType temporalType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SqlQuery<T> setParameter(Parameter<Date> param, Date value, TemporalType temporalType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SqlQuery<T> setParameter(String name, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SqlQuery<T> setParameter(String name, Calendar value, TemporalType temporalType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SqlQuery<T> setParameter(String name, Date value, TemporalType temporalType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SqlQuery<T> setParameter(int position, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SqlQuery<T> setParameter(int position, Calendar value, TemporalType temporalType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SqlQuery<T> setParameter(int position, Date value, TemporalType temporalType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Set<Parameter<?>> getParameters() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Parameter<?> getParameter(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> Parameter<T> getParameter(String name, Class<T> type) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Parameter<?> getParameter(int position) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> Parameter<T> getParameter(int position, Class<T> type) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isBound(Parameter<?> param) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T getParameterValue(Parameter<T> param) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object getParameterValue(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object getParameterValue(int position) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SqlQuery<T> setFlushMode(FlushModeType flushMode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public FlushModeType getFlushMode() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SqlQuery<T> setLockMode(LockModeType lockMode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public LockModeType getLockMode() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T unwrap(Class<T> klass) {
+ throw new PersistenceException("Unsupported class: " + klass);
+ }
+}
diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlUnit.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlUnit.java
new file mode 100644
index 0000000..2879f5d
--- /dev/null
+++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlUnit.java
@@ -0,0 +1,18 @@
+package io.trygvis.persistence.sql;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+public class SqlUnit {
+
+ private final List<SqlEntityMeta> entities;
+
+ public SqlUnit(SqlEntityMeta... entities) {
+ this.entities = asList(entities);
+ }
+
+ public List<SqlEntityMeta> getEntities() {
+ return entities;
+ }
+}