diff options
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; + } +} |