Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RequestScope support (for #85) #107

Merged
merged 1 commit into from
May 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class BeanReader {
private boolean beanLifeCycle;
private boolean primary;
private boolean secondary;
private boolean requestScopedBean;

BeanReader(TypeElement beanType, ProcessingContext context) {
this.beanType = beanType;
Expand All @@ -62,6 +63,7 @@ public String toString() {
private void init() {
typeReader.process();
beanLifeCycle = typeReader.isBeanLifeCycle();
requestScopedBean = typeReader.isRequestScopeBean();
name = typeReader.getName();
primary = (beanType.getAnnotation(Primary.class) != null);
secondary = !primary && (beanType.getAnnotation(Secondary.class) != null);
Expand Down Expand Up @@ -118,6 +120,10 @@ void read(boolean factory) {
for (MethodReader factoryMethod : factoryMethods) {
factoryMethod.addImports(importTypes);
}
if (requestScopedBean) {
importTypes.add(Constants.REQUESTSCOPEPROVIDER);
importTypes.add(Constants.REQUESTSCOPE);
}
}

private MethodReader findConstructor() {
Expand Down Expand Up @@ -256,7 +262,7 @@ boolean isLifecycleRequired() {
* Return true if lifecycle via annotated methods is required.
*/
boolean isLifecycleWrapperRequired() {
return postConstructMethod != null || preDestroyMethod != null;
return !requestScopedBean && (postConstructMethod != null || preDestroyMethod != null);
}

List<MetaData> createFactoryMethodMeta() {
Expand Down Expand Up @@ -349,12 +355,16 @@ void setWrittenToFile() {
* <p>
* If request scoped then generate a BeanFactory instead.
*/
boolean isRequestScoped() {
return requestParams.isRequestScoped();
boolean isRequestScopedController() {
return requestParams.isRequestScopedController();
}

boolean isRequestScopedBean() {
return requestScopedBean;
}

String suffix() {
return isRequestScoped() ? "$factory" : "$di";
return isRequestScopedController() ? "$factory" : "$di";
}

/**
Expand Down Expand Up @@ -396,4 +406,39 @@ void writeRequestCreate(Append writer) {
writer.append(" }").eol();
}

void buildReq(Append writer) {
writer.append(" builder.requestScope(%s.class, new RequestScopeProvider<%s>() {", shortName, shortName).eol();
writer.append(" @Override").eol();
writer.append(" public %s provide(RequestScope scope) {", shortName).eol();
}

void buildReqEnd(Append writer) {
writer.append(" return bean;").eol();
writer.append(" }").eol();
writer.append(" }");

final String ifaceTypes = typeReader.getTypesRegister();
if (ifaceTypes != null) {
if (name != null && !name.isEmpty()) {
writer.append(", \"%s\"", name);
writer.append(", ");
writer.append(ifaceTypes);
}
}
writer.append(");").eol();
}

void writePostConstruct(Append writer) {
if (postConstructMethod != null) {
writer.append(" bean.%s();", postConstructMethod.getSimpleName()).eol();
}
}

void writePreDestroy(Append writer) {
if (preDestroyMethod != null) {
writer.append(" scope.addClosable(bean::%s);", preDestroyMethod.getSimpleName()).eol();
} else if (typeReader.isClosable()) {
writer.append(" scope.addClosable(bean);").eol();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ boolean check(String paramType) {
/**
* Return true if the bean has request scoped dependencies.
*/
boolean isRequestScoped() {
boolean isRequestScopedController() {
return reqScopeHandler != null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@

class Constants {

static final String IO_CLOSEABLE = "java.io.Closeable";
static final String OPTIONAL = "java.util.Optional";
static final String KOTLIN_METADATA = "kotlin.Metadata";

static final String PROVIDER = "jakarta.inject.Provider";
static final String SINGLETON = "jakarta.inject.Singleton";
static final String INJECT = "jakarta.inject.Inject";
static final String REQUEST = "io.avaje.inject.Request";

static final String PATH = "io.avaje.http.api.Path";
static final String CONTROLLER = "io.avaje.http.api.Controller";
static final String REQUESTSCOPEPROVIDER = "io.avaje.inject.RequestScopeProvider";

static final String AT_SINGLETON = "@Singleton";
static final String AT_GENERATED = "@Generated(\"io.avaje.inject.generator\")";
static final String META_INF_FACTORY = "META-INF/services/io.avaje.inject.spi.BeanContextFactory";

static final String REQUESTSCOPE = "io.avaje.inject.RequestScope";
static final String BEANCONTEXT = "io.avaje.inject.BeanContext";
static final String CONTEXTMODULE = "io.avaje.inject.ContextModule";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ void addImports(Set<String> importTypes) {
importTypes.add(fieldType);
}

String builderGetDependency() {
String builderGetDependency(String builderRef) {
StringBuilder sb = new StringBuilder();
sb.append("b.").append(type.getMethod(nullable));
sb.append(builderRef).append(".").append(type.getMethod(nullable));
sb.append(nm(fieldType)).append(".class");
if (name != null) {
sb.append(",\"").append(name).append("\"");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.avaje.inject.ContextModule;
import io.avaje.inject.Factory;
import io.avaje.inject.Request;
import io.avaje.inject.spi.DependencyMeta;

import javax.annotation.processing.AbstractProcessor;
Expand Down Expand Up @@ -78,11 +79,13 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment

Set<? extends Element> factoryBeans = roundEnv.getElementsAnnotatedWith(Factory.class);
Set<? extends Element> beans = roundEnv.getElementsAnnotatedWith(Singleton.class);
Set<? extends Element> requestBeans = roundEnv.getElementsAnnotatedWith(Request.class);

readModule(roundEnv);
readChangedBeans(factoryBeans, true);
readChangedBeans(beans, false);
readChangedBeans(controllers, false);
readChangedBeans(requestBeans, false);

mergeMetaData();

Expand Down Expand Up @@ -151,7 +154,7 @@ private void readChangedBeans(Set<? extends Element> beans, boolean factory) {
*/
private void mergeMetaData() {
for (BeanReader beanReader : beanReaders) {
if (beanReader.isRequestScoped()) {
if (beanReader.isRequestScopedController()) {
context.logDebug("skipping request scoped processed bean " + beanReader);
} else {
String metaKey = beanReader.getMetaKey();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class SimpleBeanWriter {
private static final String CODE_COMMENT_FACTORY = "/**\n * Generated source - dependency injection factory for request scoped %s.\n */";
private static final String CODE_COMMENT_LIFECYCLE = " /**\n * Lifecycle wrapper for %s.\n */";
private static final String CODE_COMMENT_BUILD = " /**\n * Create and register %s.\n */";
private static final String CODE_COMMENT_BUILD_RSB = " /**\n * Register provider for request scoped %s.\n */";

private final BeanReader beanReader;
private final ProcessingContext context;
Expand All @@ -24,6 +25,7 @@ class SimpleBeanWriter {
private final String packageName;
private final String suffix;
private Append writer;
private String indent = " ";

SimpleBeanWriter(BeanReader beanReader, ProcessingContext context) {
this.beanReader = beanReader;
Expand All @@ -45,7 +47,7 @@ void write() throws IOException {
writePackage();
writeImports();
writeClassStart();
if (isRequestScoped()) {
if (isRequestScopedController()) {
writeRequestCreate();
} else {
writeStaticFactoryMethod();
Expand All @@ -61,8 +63,8 @@ private void writeRequestCreate() {
beanReader.writeRequestCreate(writer);
}

private boolean isRequestScoped() {
return beanReader.isRequestScoped();
private boolean isRequestScopedController() {
return beanReader.isRequestScopedController();
}

private void writeStaticFactoryBeanMethods() {
Expand All @@ -89,15 +91,52 @@ private void writeFactoryBeanMethod(MethodReader method) {
}

private void writeStaticFactoryMethod() {

MethodReader constructor = beanReader.getConstructor();
if (constructor == null) {
context.logError(beanReader.getBeanType(), "Unable to determine constructor to use for %s? Add explicit @Inject to one of the constructors.", beanReader.getBeanType());
return;
}
writeBuildMethodStart(constructor);
if (beanReader.isRequestScopedBean()) {
writeReqScopeBean(constructor);
} else {
writeAddFor(constructor);
}
writer.append(" }").eol().eol();
}

private void writeReqScopeBean(MethodReader constructor) {
indent = indent + " ";
beanReader.buildReq(writer);
writeCreateBean(constructor, "scope");
if (beanReader.isExtraInjectionRequired()) {
writeExtraInjection();
}
beanReader.writePostConstruct(writer);
beanReader.writePreDestroy(writer);
beanReader.buildReqEnd(writer);
}

private void writeAddFor(MethodReader constructor) {
beanReader.buildAddFor(writer);
writeCreateBean(constructor, "builder");
beanReader.buildRegister(writer);
if (beanReader.isLifecycleRequired()) {
beanReader.buildAddLifecycle(writer);
}
if (beanReader.isExtraInjectionRequired()) {
writeExtraInjection();
}
writer.append(" }").eol();
}

private void writeBuildMethodStart(MethodReader constructor) {
int providerIndex = 0;
writer.append(CODE_COMMENT_BUILD, shortName).eol();
if (beanReader.isRequestScopedBean()) {
writer.append(CODE_COMMENT_BUILD_RSB, shortName).eol();
} else {
writer.append(CODE_COMMENT_BUILD, shortName).eol();
}
writer.append(" public static void build(Builder builder");
for (MethodReader.MethodParam param : constructor.getParams()) {
if (param.isGenericParam()) {
Expand All @@ -112,47 +151,47 @@ private void writeStaticFactoryMethod() {
}
}
writer.append(") {").eol();
}

beanReader.buildAddFor(writer);
writer.append(" %s bean = new %s(", shortName, shortName);

private void writeCreateBean(MethodReader constructor, String builderName) {
writer.append("%s %s bean = new %s(", indent, shortName, shortName);
// add constructor dependencies
List<MethodReader.MethodParam> params = constructor.getParams();
for (int i = 0; i < params.size(); i++) {
writeMethodParams(builderName, constructor);
}

private void writeExtraInjection() {
String builderRef = "b";
String beanRef = "$bean";
if (beanReader.isRequestScopedBean()) {
builderRef = "scope";
beanRef = "bean";
} else {
writer.append(" builder.addInjector(b -> {").eol();
}
writer.append(" // field and method injection").eol();
for (FieldReader fieldReader : beanReader.getInjectFields()) {
String fieldName = fieldReader.getFieldName();
String getDependency = fieldReader.builderGetDependency(builderRef);
writer.append(" %s.%s = %s;", beanRef, fieldName, getDependency).eol();
}
for (MethodReader methodReader : beanReader.getInjectMethods()) {
writer.append(" %s.%s(", beanRef, methodReader.getName());
writeMethodParams(builderRef, methodReader);
}
if (!beanReader.isRequestScopedBean()) {
writer.append(" });").eol();
}
}

private void writeMethodParams(String builderRef, MethodReader methodReader) {
List<MethodReader.MethodParam> methodParams = methodReader.getParams();
for (int i = 0; i < methodParams.size(); i++) {
if (i > 0) {
writer.append(", ");
}
writer.append(params.get(i).builderGetDependency("builder"));
writer.append(methodParams.get(i).builderGetDependency(builderRef));
}
writer.append(");").eol();

beanReader.buildRegister(writer);
if (beanReader.isLifecycleRequired()) {
beanReader.buildAddLifecycle(writer);
}
if (beanReader.isExtraInjectionRequired()) {
writer.append(" builder.addInjector(b -> {").eol();
writer.append(" // field and method injection").eol();
for (FieldReader fieldReader : beanReader.getInjectFields()) {
String fieldName = fieldReader.getFieldName();
String getDependency = fieldReader.builderGetDependency();
writer.append(" $bean.%s = %s;", fieldName, getDependency).eol();
}
for (MethodReader methodReader : beanReader.getInjectMethods()) {
writer.append(" $bean.%s(", methodReader.getName());
List<MethodReader.MethodParam> methodParams = methodReader.getParams();
for (int i = 0; i < methodParams.size(); i++) {
if (i > 0) {
writer.append(", ");
}
writer.append(methodParams.get(i).builderGetDependency("b"));
}
writer.append(");").eol();
}
writer.append(" });").eol();
}
writer.append(" }").eol();
writer.append(" }").eol().eol();
}

private void writeImports() {
Expand Down Expand Up @@ -189,20 +228,20 @@ private void writeClassEnd() {
}

private void writeClassStart() {
if (beanReader.isRequestScoped()) {
if (beanReader.isRequestScopedController()) {
writer.append(CODE_COMMENT_FACTORY, shortName).eol();
} else {
writer.append(CODE_COMMENT, shortName).eol();
}
writer.append(Constants.AT_GENERATED).eol();
if (beanReader.isRequestScoped()) {
if (beanReader.isRequestScopedController()) {
writer.append(Constants.AT_SINGLETON).eol();
}
writer.append("public class ").append(shortName).append(suffix).append(" ");
if (beanReader.isLifecycleWrapperRequired()) {
writer.append("implements BeanLifecycle ");
}
if (beanReader.isRequestScoped()) {
if (beanReader.isRequestScopedController()) {
writer.append("implements ");
beanReader.factoryInterface(writer);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class TypeAnnotationReader {
private final ProcessingContext context;
private final List<String> annotationTypes = new ArrayList<>();
private String qualifierName;
private boolean requestScopeBean;

TypeAnnotationReader(TypeElement beanType, ProcessingContext context) {
this.beanType = beanType;
Expand All @@ -35,6 +36,10 @@ String getQualifierName() {
return qualifierName;
}

boolean isRequestScopeBean() {
return requestScopeBean;
}

void process() {
for (AnnotationMirror annotationMirror : beanType.getAnnotationMirrors()) {
DeclaredType annotationType = annotationMirror.getAnnotationType();
Expand All @@ -45,7 +50,9 @@ void process() {
} else if (annType.indexOf('.') == -1) {
context.logWarn("skip when no package on annotation " + annType);
} else {
if (IncludeAnnotations.include(annType)) {
if (Constants.REQUEST.equals(annType)) {
requestScopeBean = true;
} else if (IncludeAnnotations.include(annType)) {
annotationTypes.add(annType);
}
}
Expand Down
Loading