From 6ba3b6fc452265fb595b7f32055423c606ed77fd Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Thu, 1 Aug 2013 22:26:13 +0200 Subject: o Initial import. --- .gitignore | 3 + container-compiler-plugin/pom.xml | 19 ++ .../io/trygvis/container/compiler/MyProcessor.java | 246 +++++++++++++++++++++ .../trygvis/container/compiler/model/ClassG.java | 157 +++++++++++++ .../trygvis/container/compiler/model/FieldRef.java | 31 +++ .../container/compiler/model/MethodRef.java | 16 ++ .../container/compiler/model/ParameterRef.java | 11 + .../trygvis/container/compiler/model/TypeRef.java | 83 +++++++ container-core/pom.xml | 32 +++ .../main/java/io/trygvis/container/log/Log.java | 4 + .../container/tx/PlatformTransactionManager.java | 188 ++++++++++++++++ .../io/trygvis/container/tx/TransactionHolder.java | 7 + .../src/main/java/io/trygvis/container/tx/Tx.java | 5 + myapp/pom.xml | 36 +++ .../io/trygvis/container/myapp/MyComponent.java | 40 ++++ .../io/trygvis/container/myapp/MyComponent_Tx.java | 26 +++ .../java/io/trygvis/container/myapp/MyMain.java | 10 + pom.xml | 31 +++ 18 files changed, 945 insertions(+) create mode 100644 .gitignore create mode 100644 container-compiler-plugin/pom.xml create mode 100644 container-compiler-plugin/src/main/java/io/trygvis/container/compiler/MyProcessor.java create mode 100644 container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/ClassG.java create mode 100644 container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/FieldRef.java create mode 100644 container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/MethodRef.java create mode 100644 container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/ParameterRef.java create mode 100644 container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/TypeRef.java create mode 100644 container-core/pom.xml create mode 100644 container-core/src/main/java/io/trygvis/container/log/Log.java create mode 100644 container-core/src/main/java/io/trygvis/container/tx/PlatformTransactionManager.java create mode 100644 container-core/src/main/java/io/trygvis/container/tx/TransactionHolder.java create mode 100644 container-core/src/main/java/io/trygvis/container/tx/Tx.java create mode 100644 myapp/pom.xml create mode 100644 myapp/src/main/java/io/trygvis/container/myapp/MyComponent.java create mode 100644 myapp/src/main/java/io/trygvis/container/myapp/MyComponent_Tx.java create mode 100644 myapp/src/main/java/io/trygvis/container/myapp/MyMain.java create mode 100644 pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..612c5bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +.idea +*.iml diff --git a/container-compiler-plugin/pom.xml b/container-compiler-plugin/pom.xml new file mode 100644 index 0000000..beb4b55 --- /dev/null +++ b/container-compiler-plugin/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + container-playground + io.trygvis.container + 1.0-SNAPSHOT + + container-compiler-plugin + + + io.trygvis.container + container-core + ${project.version} + + + 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 new file mode 100644 index 0000000..0ec7001 --- /dev/null +++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/MyProcessor.java @@ -0,0 +1,246 @@ +package io.trygvis.container.compiler; + +import io.trygvis.container.compiler.model.ClassG; +import io.trygvis.container.compiler.model.FieldRef; +import io.trygvis.container.compiler.model.ParameterRef; +import io.trygvis.container.compiler.model.TypeRef; +import io.trygvis.container.log.Log; +import io.trygvis.container.tx.PlatformTransactionManager; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.processing.Completion; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.JavaFileObject; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static javax.lang.model.util.ElementFilter.constructorsIn; +import static org.springframework.util.StringUtils.collectionToDelimitedString; + +public class MyProcessor implements Processor { + + private ProcessingEnvironment processingEnv; + private Elements elements; + private Types types; + + @Override + public Set getSupportedOptions() { + return emptySet(); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.RELEASE_5; + } + + @Override + public void init(ProcessingEnvironment processingEnv) { + this.processingEnv = processingEnv; + elements = processingEnv.getElementUtils(); + types = processingEnv.getTypeUtils(); + } + + @Override + public Iterable getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) { + return emptyList(); + } + + @Override + public Set getSupportedAnnotationTypes() { + return new HashSet<>(asList(Transactional.class.getName(), Log.class.getName())); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + Set transactional = roundEnv.getElementsAnnotatedWith(Transactional.class); + + TypeElement tx = elements.getTypeElement(Transactional.class.getCanonicalName()); + TypeElement log = elements.getTypeElement(Transactional.class.getCanonicalName()); + + for (Element element : transactional) { + try { + System.out.println("Processing: " + element); + for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { + + System.out.println("annotationMirror.getAnnotationType().asElement().getSimpleName() = " + annotationMirror.getAnnotationType().asElement().getSimpleName()); + + if (types.isSameType(tx.asType(), annotationMirror.getAnnotationType())) { + processTransactional((TypeElement) element); + } +// if (types.isSameType(log.asType(), annotationMirror.getAnnotationType())) { +// processLog((TypeElement) element); +// } + } + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return true; + } + + private void processTransactional(TypeElement element) throws IOException { + Name targetClassName = element.getSimpleName(); + System.out.println("Processing @Transactional " + targetClassName); + + Transactional transactional = element.getAnnotation(Transactional.class); + + String p = elements.getPackageOf(element).getQualifiedName().toString(); + + String className = targetClassName + "_Transactional"; + + JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(p + "." + className, element); + + try (PrintWriter w = new PrintWriter(sourceFile.openWriter())) { + ClassG g = new ClassG(p, className, targetClassName.toString()); +// writer.println("package " + p + ";"); +// writer.println(); +// writer.println("import io.trygvis.container.tx.PlatformTransactionManager;"); +// writer.println(); +// writer.println("public class " + className + " extends " + targetClassName + " {"); +// writer.println(" private final PlatformTransactionManager transactionManager;"); +// writer.println(); + + TypeRef platformTransactionManager = g.addImport(PlatformTransactionManager.class); + FieldRef transactionManager = g.addField(PlatformTransactionManager.class, "transactionManager"); + + for (ExecutableElement constructor : constructorsIn(elements.getAllMembers(element))) { + if (!constructor.getModifiers().contains(Modifier.PUBLIC)) { + continue; + } + + constructor(g, constructor); + } + +// System.out.println("elements.getTypeElement(\"Object\") = " + elements.getTypeElement("java.lang.Object")); + Name javaLangObjectName = elements.getTypeElement("java.lang.Object").getQualifiedName(); + + for (Element e : elements.getAllMembers(element)) { + if (!(e instanceof ExecutableElement)) { + continue; + } + + ExecutableElement ee = (ExecutableElement) e; + + TypeElement enclosingElement = (TypeElement) ee.getEnclosingElement(); + + if (enclosingElement.getQualifiedName().equals(javaLangObjectName)) { + continue; + } + + if (ee.getSimpleName().toString().equals("")) { + continue; + } + +// System.out.println("ee.getSimpleName() = " + ee.getSimpleName()); +// System.out.println("ee.getEnclosingElement() = " + enclosingElement); +// System.out.println("ee.getEnclosingElement().getQualifiedName() = " + enclosingElement.getQualifiedName()); +// System.out.println("ee.getEnclosingElement().asType().getKind().getDeclaringClass() = " + enclosingElement.asType().getKind().getDeclaringClass()); + + TypeMirror returnTypeMirror = ee.getReturnType(); + + TypeKind kind = returnTypeMirror.getKind(); + + boolean isVoid = kind == TypeKind.VOID; + TypeRef returnType = isVoid ? TypeRef.VOID : g.addImport(returnTypeMirror); +// String returnString; +// if (isVoid) { +// returnString = "void"; +// } else { +// returnString = returnTypeMirror.toString(); +// } + + List parameters = new ArrayList<>(); + List arguments = new ArrayList<>(); + for (VariableElement ve : ee.getParameters()) { +// parameters.add("final " + ve.asType().toString() + " " + ve.getSimpleName().toString()); + TypeRef k = g.addImport(ve.asType()); + parameters.add(new ParameterRef(k, ve.getSimpleName().toString())); + arguments.add(ve.getSimpleName().toString()); + } + + CharArrayWriter buffer = new CharArrayWriter(); + PrintWriter m = new PrintWriter(buffer); + g.addMethod(buffer.toString(), returnType, ee.getSimpleName().toString(), parameters.toArray(new ParameterRef[arguments.size()])); + +// writer.println(); +// writer.println(" public " + returnString + " " + ee.getSimpleName() + "(" + collectionToDelimitedString(parameters, ", ") + ") {"); + +/* + writer.println(" " + (isVoid ? "" : "return ") + "transactionManager.doInTransaction("); + writer.println(" PlatformTransactionManager.TransactionIsolation.ISOLATION_" + transactional.isolation() + ","); + writer.println(" PlatformTransactionManager.TransactionPropagation.PROPAGATION_" + transactional.propagation() + ","); + writer.println(" new PlatformTransactionManager.TransactionTemplate<" + (isVoid ? "Object" : returnTypeMirror) + ">() {"); + writer.println(" @Override"); + writer.println(" public " + (isVoid ? "Object" : returnTypeMirror) + " doInTransaction() {"); + String targetInvocation = className + ".super." + ee.getSimpleName() + "(" + collectionToDelimitedString(arguments, ", ") + ");"; + + if (isVoid) { + writer.println(" " + targetInvocation); + writer.println(" return null;"); + } else { + writer.print(" return "); + writer.println(targetInvocation); + } + + writer.println(" }"); + writer.println(" });"); + writer.println(" }"); +*/ + + elements.printElements(new PrintWriter(System.out), ee); + } +// writer.println("}"); + g.write(w); + } + } + + private void constructor(ClassG g, ExecutableElement constructor) { + List parameters = new ArrayList<>(); + List goesToSuper = new ArrayList<>(); + + TypeRef k = g.addImport(PlatformTransactionManager.class); + parameters.add(new ParameterRef(k, "_transactionManager_")); + + for (VariableElement p : constructor.getParameters()) { +// parameters.add(p.asType().toString() + " " + p.getSimpleName().toString()); +// arguments.add(p.getSimpleName().toString()); + k = g.addImport(p.asType()); + String name = p.getSimpleName().toString(); + parameters.add(new ParameterRef(k, name)); + goesToSuper.add(name); + } + +// String params = parameters.size() == 0 ? "" : ", " + collectionToDelimitedString(parameters, ", "); + + g.simpleConstructor(parameters, goesToSuper); + +// writer.println(" public " + className + "(PlatformTransactionManager _transactionManager_" + params + ") {"); +// writer.println(" super(" + collectionToDelimitedString(arguments, ", ") + ");"); +// writer.println(" this.transactionManager = _transactionManager_;"); +// writer.println(" }"); + } +} 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 new file mode 100644 index 0000000..6c0e246 --- /dev/null +++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/ClassG.java @@ -0,0 +1,157 @@ +package io.trygvis.container.compiler.model; + +import javax.lang.model.type.TypeMirror; +import java.io.CharArrayWriter; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import static org.springframework.util.StringUtils.collectionToDelimitedString; + +public class ClassG { + private final String packageName; + private final String className; + private final String extendsClass; + private final Set imports = new TreeSet<>(); + private final Set fields = new TreeSet<>(); + private final List methods = new ArrayList<>(); + +// public ClassG(String packageName, String className) { +// this(packageName, className, null); +// } + + public ClassG(String packageName, String className, String extendsClass) { + this.packageName = packageName; + this.className = className; + this.extendsClass = extendsClass; + } + + public TypeRef addImport(final TypeMirror type) { + if (type.getKind().isPrimitive()) { + return TypeRef.find(type.getKind()); + } + + String canonicalName = type.toString(); + System.out.println("canonicalName = " + canonicalName); + + for (TypeRef i : imports) { + if (i.canonicalName().equals(canonicalName)) { + return i; + } + } + + TypeRef ref = new TypeRef(canonicalName, canonicalName); + imports.add(ref); + return ref; + } + + public TypeRef addImport(Class klass) { +// if (klass.isPrimitive()) { +// return TypeRef.PRIMITIVE; +// } + + String canonicalName = klass.getCanonicalName(); + String simpleName = klass.getSimpleName(); + String name = simpleName; + for (TypeRef i : imports) { + if (i.canonicalName().equals(canonicalName)) { + return i; + } + + if (i.name.equals(simpleName)) { + name = canonicalName; + break; + } + } + + TypeRef ref = new TypeRef(name, canonicalName); + imports.add(ref); + + return ref; + } + + public FieldRef addField(Class klass, String name) { + TypeRef type = addImport(klass); + FieldRef ref = new FieldRef(type, name); + fields.add(ref); + return ref; + } + + public MethodRef addMethod(String body, TypeRef returnType, String name, ParameterRef... parameters) { + MethodRef ref = new MethodRef(returnType, name, parameters, body); + methods.add(ref); + return ref; + } + + public void write(PrintWriter writer) { + writer.println("package " + packageName + ";"); + writer.println(); + for (TypeRef i : imports) { + if (i.isPrimitive()) { + continue; + } + writer.println("import " + i.canonicalName() + ";"); + } + writer.println(); + writer.println("public class " + className + " extends " + extendsClass + " {"); + for (FieldRef field : fields) { + writer.println(" private final " + field.klass.name + " " + field.name + ";"); + } + + for (MethodRef method : methods) { + write(writer, method); + } + + writer.println("}"); + } + + private void write(PrintWriter writer, MethodRef method) { + String returnString; + if (method.returnType == TypeRef.VOID) { + returnString = "void"; + } else { + returnString = method.returnType.name; + } + + List parameters = new ArrayList<>(); + for (ParameterRef p : method.parameters) { + parameters.add(p.klass.name + " " + p.name); + } + + writer.println(" public " + returnString + " " + method.name + "(" + collectionToDelimitedString(parameters, ", ") + ") {"); + writer.println(method.body); + writer.println(" }"); + } + + public void simpleConstructor(List parameters, List goesToSuper) { + List ps = new ArrayList<>(); + for (ParameterRef p : parameters) { + ps.add(p.klass.name + " " + p.name); + } + List ss = new ArrayList<>(); + List local = new ArrayList<>(); + for (String s : goesToSuper) { + for (ParameterRef parameter : parameters) { + if (parameter.name.equals(s)) { + ss.add(parameter.name); + continue; + } + } + local.add(s); + } + String params = ps.size() == 0 ? "" : ", " + collectionToDelimitedString(ps, ", "); + + CharArrayWriter buffer = new CharArrayWriter(); + PrintWriter writer = new PrintWriter(buffer); + + writer.println(" public " + className + "(" + params + ") {"); + writer.println(" super(" + collectionToDelimitedString(ss, ", ") + ");"); +// writer.println(" this.transactionManager = _transactionManager_;"); + for (String s : local) { + writer.println(" this." + s + " = " + s); + } + writer.println(" }"); + } +} diff --git a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/FieldRef.java b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/FieldRef.java new file mode 100644 index 0000000..29a2c51 --- /dev/null +++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/FieldRef.java @@ -0,0 +1,31 @@ +package io.trygvis.container.compiler.model; + +public class FieldRef implements Comparable { + public final TypeRef klass; + public final String name; + + public FieldRef(TypeRef klass, String name) { + this.klass = klass; + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof FieldRef)) return false; + + FieldRef fieldRef = (FieldRef) o; + + return name.equals(fieldRef.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public int compareTo(FieldRef o) { + return name.compareTo(o.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 new file mode 100644 index 0000000..0b2cf89 --- /dev/null +++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/MethodRef.java @@ -0,0 +1,16 @@ +package io.trygvis.container.compiler.model; + +public class MethodRef { + public final TypeRef returnType; + public final String name; + public final ParameterRef[] parameters; + public final String body; + + public + MethodRef(TypeRef returnType, String name, ParameterRef[] parameters, String body) { + this.returnType = returnType; + this.name = name; + this.parameters = parameters; + this.body = body; + } +} diff --git a/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/ParameterRef.java b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/ParameterRef.java new file mode 100644 index 0000000..fc69cb1 --- /dev/null +++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/ParameterRef.java @@ -0,0 +1,11 @@ +package io.trygvis.container.compiler.model; + +public class ParameterRef { + public final TypeRef klass; + public final String name; + + public ParameterRef(TypeRef klass, String name) { + this.klass = klass; + this.name = name; + } +} 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 new file mode 100644 index 0000000..aabfb45 --- /dev/null +++ b/container-compiler-plugin/src/main/java/io/trygvis/container/compiler/model/TypeRef.java @@ -0,0 +1,83 @@ +package io.trygvis.container.compiler.model; + +import javax.lang.model.type.TypeKind; + +public class TypeRef implements Comparable { + + public static final TypeRef VOID = new TypeRef("void", "void"); + public static final TypeRef BOOLEAN = new TypeRef("boolean", null); + public static final TypeRef BYTE = new TypeRef("byte", null); + public static final TypeRef SHORT = new TypeRef("short", null); + public static final TypeRef CHAR = new TypeRef("char", null); + public static final TypeRef INT = new TypeRef("int", null); + public static final TypeRef LONG = new TypeRef("long", null); + public static final TypeRef FLOAT = new TypeRef("float", null); + public static final TypeRef DOUBLE = new TypeRef("double", null); + + /** + * The name of a class used within a class file. Is either the simple name or the canonical name. + */ + public final String name; + + private final String canonicalName; + + public String canonicalName() { + if (canonicalName == null) { + throw new RuntimeException("This type doesn't have a canonical name"); + } + + return canonicalName; + } + + public boolean isPrimitive() { + return canonicalName == null; + } + + public TypeRef(String name, String canonicalName) { + this.name = name; + this.canonicalName = canonicalName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TypeRef)) return false; + + TypeRef classRef = (TypeRef) o; + + return canonicalName.equals(classRef.canonicalName); + } + + @Override + public int hashCode() { + return canonicalName.hashCode(); + } + + @Override + public int compareTo(TypeRef o) { + return canonicalName.compareTo(o.canonicalName); + } + + public static TypeRef find(TypeKind kind) { + switch (kind) { + case BOOLEAN: + return BOOLEAN; + case BYTE: + return BYTE; + case SHORT: + return SHORT; + case CHAR: + return CHAR; + case INT: + return INT; + case LONG: + return LONG; + case FLOAT: + return FLOAT; + case DOUBLE: + return DOUBLE; + } + + throw new RuntimeException("Unknown kind: " + kind); + } +} diff --git a/container-core/pom.xml b/container-core/pom.xml new file mode 100644 index 0000000..e16c761 --- /dev/null +++ b/container-core/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + container-playground + io.trygvis.container + 1.0-SNAPSHOT + + container-core + + 3.2.2.RELEASE + + + + org.springframework + spring-beans + ${version.spring} + + + org.springframework + spring-jdbc + ${version.spring} + + + org.springframework + spring-tx + ${version.spring} + + + diff --git a/container-core/src/main/java/io/trygvis/container/log/Log.java b/container-core/src/main/java/io/trygvis/container/log/Log.java new file mode 100644 index 0000000..71e81d1 --- /dev/null +++ b/container-core/src/main/java/io/trygvis/container/log/Log.java @@ -0,0 +1,4 @@ +package io.trygvis.container.log; + +public @interface Log { +} diff --git a/container-core/src/main/java/io/trygvis/container/tx/PlatformTransactionManager.java b/container-core/src/main/java/io/trygvis/container/tx/PlatformTransactionManager.java new file mode 100644 index 0000000..3ab7b6f --- /dev/null +++ b/container-core/src/main/java/io/trygvis/container/tx/PlatformTransactionManager.java @@ -0,0 +1,188 @@ +package io.trygvis.container.tx; + +public class PlatformTransactionManager { + + public static Tx currentTx() { + return tx.get().tx(); + } + + public static interface TxFactory { + Tx create(TransactionIsolation isolation); + } + + static final ThreadLocal tx = new ThreadLocal() { + @Override + protected TxEntry initialValue() { + return new RootTxState(); + } + }; + + public abstract static class TxEntry { + abstract Tx tx(); + + abstract boolean isActive(); + + abstract TxEntry pop(); + } + + public static class NewTxTxEntry extends TxEntry { + private final TxEntry parent; + private final Tx tx; + + public NewTxTxEntry(TxEntry parent, Tx tx) { + this.parent = parent; + this.tx = tx; + } + + @Override + Tx tx() { + return tx; + } + + public boolean isActive() { + return true; + } + + @Override + TxEntry pop() { + return parent; + } + } + + public static class NestedTxEntry extends TxEntry { + private final NestedTxEntry parent; + + public NestedTxEntry(NestedTxEntry parent) { + this.parent = parent; + } + + @Override + Tx tx() { + return parent.tx(); + } + + public boolean isActive() { + return parent.isActive(); + } + + @Override + TxEntry pop() { + return parent; + } + } + + private final static class InactiveTxEntry extends TxEntry { + private final TxEntry parent; + + private InactiveTxEntry(TxEntry parent) { + this.parent = parent; + } + + @Override + public Tx tx() { + throw new RuntimeException("No transaction currently running."); + } + + @Override + public boolean isActive() { + return false; + } + + @Override + TxEntry pop() { + return parent; + } + } + + private final static class RootTxState extends TxEntry { + @Override + public Tx tx() { + throw new RuntimeException("No transaction currently running."); + } + + @Override + public boolean isActive() { + return false; + } + + @Override + TxEntry pop() { + throw new RuntimeException("Trying to pop root entry"); + } + } + + public enum TransactionIsolation { + ISOLATION_DEFAULT, + ISOLATION_READ_COMMITTED, + ISOLATION_READ_UNCOMMITTED, + ISOLATION_REPEATABLE_READ, + ISOLATION_SERIALIZABLE + } + + public enum TransactionPropagation { + PROPAGATION_NESTED, + // PROPAGATION_NEVER, + PROPAGATION_NOT_SUPPORTED, + PROPAGATION_REQUIRED, + PROPAGATION_REQUIRES_NEW, + PROPAGATION_SUPPORTS + } + + private final TxFactory txFactory; + private final TransactionPropagation defaultPropagation; + + public PlatformTransactionManager(TxFactory txFactory, TransactionPropagation defaultPropagation) { + this.txFactory = txFactory; + this.defaultPropagation = defaultPropagation; + } + + public T doInTransaction(TransactionIsolation isolation, TransactionPropagation propagation, TransactionTemplate transactionTemplate) { + final TxEntry prevEntry = PlatformTransactionManager.tx.get(); + final TxEntry currentEntry = nextTxEntry(prevEntry, isolation, propagation); + PlatformTransactionManager.tx.set(currentEntry); + + try { + return transactionTemplate.doInTransaction(); + } finally { + PlatformTransactionManager.tx.set(prevEntry); + } + } + + private TxEntry nextTxEntry(TxEntry prevEntry, TransactionIsolation isolation, TransactionPropagation propagation) { + TxEntry currentEntry; + switch (propagation) { + case PROPAGATION_NESTED: + throw new RuntimeException("Propagation 'nested' not supported."); + case PROPAGATION_NOT_SUPPORTED: + currentEntry = new InactiveTxEntry(prevEntry); + PlatformTransactionManager.tx.set(currentEntry); + break; + default: + throw new RuntimeException("Unknown transaction propagation: " + propagation); + case PROPAGATION_REQUIRED: + if (prevEntry.isActive()) { + currentEntry = new NestedTxEntry((NestedTxEntry) prevEntry); + } else { + Tx tx = txFactory.create(isolation); + currentEntry = new NewTxTxEntry(prevEntry, tx); + } + break; + case PROPAGATION_REQUIRES_NEW: + Tx tx = txFactory.create(isolation); + currentEntry = new NewTxTxEntry(prevEntry, tx); + break; + case PROPAGATION_SUPPORTS: + if (prevEntry.isActive()) { + currentEntry = new NestedTxEntry((NestedTxEntry) prevEntry); + } else { + currentEntry = new InactiveTxEntry(prevEntry); + } + break; + } + return currentEntry; + } + + public interface TransactionTemplate { + T doInTransaction(); + } +} diff --git a/container-core/src/main/java/io/trygvis/container/tx/TransactionHolder.java b/container-core/src/main/java/io/trygvis/container/tx/TransactionHolder.java new file mode 100644 index 0000000..9f7e441 --- /dev/null +++ b/container-core/src/main/java/io/trygvis/container/tx/TransactionHolder.java @@ -0,0 +1,7 @@ +package io.trygvis.container.tx; + +public class TransactionHolder { + public static Tx currentTx() { + return PlatformTransactionManager.currentTx(); + } +} diff --git a/container-core/src/main/java/io/trygvis/container/tx/Tx.java b/container-core/src/main/java/io/trygvis/container/tx/Tx.java new file mode 100644 index 0000000..8ff9d05 --- /dev/null +++ b/container-core/src/main/java/io/trygvis/container/tx/Tx.java @@ -0,0 +1,5 @@ +package io.trygvis.container.tx; + +public interface Tx { + void rollback(); +} diff --git a/myapp/pom.xml b/myapp/pom.xml new file mode 100644 index 0000000..7253422 --- /dev/null +++ b/myapp/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + container-playground + io.trygvis.container + 1.0-SNAPSHOT + + myapp + + + io.trygvis.container + container-core + ${project.version} + + + io.trygvis.container + container-compiler-plugin + ${project.version} + + + + + + maven-compiler-plugin + + + io.trygvis.container.compiler.MyProcessor + + + + + + diff --git a/myapp/src/main/java/io/trygvis/container/myapp/MyComponent.java b/myapp/src/main/java/io/trygvis/container/myapp/MyComponent.java new file mode 100644 index 0000000..cf30c3b --- /dev/null +++ b/myapp/src/main/java/io/trygvis/container/myapp/MyComponent.java @@ -0,0 +1,40 @@ +package io.trygvis.container.myapp; + +import io.trygvis.container.log.Log; +import io.trygvis.container.tx.Tx; +import org.springframework.transaction.annotation.Transactional; + +import static io.trygvis.container.tx.TransactionHolder.currentTx; + +@Transactional +@Log +public class MyComponent { + private final String myString = "123"; + private String myVar; + + public static class Person { + public final String name; + + public Person(String name) { + this.name = name; + } + } + + protected MyComponent() { + } + + public MyComponent(String myVar) { + this.myVar = myVar; + } + + public void doProcessing(int x) { + Tx tx = currentTx(); + } + + public Person addPerson(String name) { + return new Person(name); + } + + public void deletePerson(int a, int b) { + } +} diff --git a/myapp/src/main/java/io/trygvis/container/myapp/MyComponent_Tx.java b/myapp/src/main/java/io/trygvis/container/myapp/MyComponent_Tx.java new file mode 100644 index 0000000..445d236 --- /dev/null +++ b/myapp/src/main/java/io/trygvis/container/myapp/MyComponent_Tx.java @@ -0,0 +1,26 @@ +package io.trygvis.container.myapp; + +import io.trygvis.container.tx.PlatformTransactionManager; + +public class MyComponent_Tx extends MyComponent { + private final PlatformTransactionManager transactionManager; + private final MyComponent target; + + public MyComponent_Tx(PlatformTransactionManager tm) { + this.transactionManager = tm; + this.target = new MyComponent(); + } + + public void doProcessing(final int x) { + transactionManager.doInTransaction( + PlatformTransactionManager.TransactionIsolation.ISOLATION_DEFAULT, + PlatformTransactionManager.TransactionPropagation.PROPAGATION_REQUIRED, + new PlatformTransactionManager.TransactionTemplate() { + @Override + public Object doInTransaction() { + target.doProcessing(x); + return null; + } + }); + } +} diff --git a/myapp/src/main/java/io/trygvis/container/myapp/MyMain.java b/myapp/src/main/java/io/trygvis/container/myapp/MyMain.java new file mode 100644 index 0000000..a54e486 --- /dev/null +++ b/myapp/src/main/java/io/trygvis/container/myapp/MyMain.java @@ -0,0 +1,10 @@ +package io.trygvis.container.myapp; + +import org.springframework.transaction.annotation.Transactional; + +public class MyMain { + + public static void main(String[] args) { + System.out.println("io.trygvis.container.myapp.MyMain.main"); + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9997562 --- /dev/null +++ b/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + pom + + io.trygvis + trygvis-parent + 1 + + io.trygvis.container + container-playground + 1.0-SNAPSHOT + + + + maven-compiler-plugin + + 1.7 + 1.7 + + + + + + container-compiler-plugin + myapp + container-core + + -- cgit v1.2.3