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

RestClientProcessor - replace generated bean classes with BeanRegistrar #691

Merged
merged 2 commits into from
Feb 1, 2019
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 @@ -19,13 +19,8 @@
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.ws.rs.Path;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.ClientResponseFilter;

Expand All @@ -37,17 +32,17 @@
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.protean.gizmo.ClassCreator;
import org.jboss.protean.gizmo.ClassOutput;
import org.jboss.protean.gizmo.MethodCreator;
import org.jboss.protean.arc.processor.BeanConfigurator;
import org.jboss.protean.arc.processor.BeanRegistrar;
import org.jboss.protean.arc.processor.ScopeInfo;
import org.jboss.protean.gizmo.MethodDescriptor;
import org.jboss.protean.gizmo.ResultHandle;
import org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy;
import org.jboss.resteasy.spi.ResteasyConfiguration;
import org.jboss.shamrock.deployment.annotations.BuildProducer;
import org.jboss.shamrock.deployment.annotations.BuildStep;
import org.jboss.shamrock.arc.deployment.AdditionalBeanBuildItem;
import org.jboss.shamrock.arc.deployment.GeneratedBeanBuildItem;
import org.jboss.shamrock.arc.deployment.BeanRegistrarBuildItem;
import org.jboss.shamrock.deployment.builditem.CombinedIndexBuildItem;
import org.jboss.shamrock.deployment.builditem.FeatureBuildItem;
import org.jboss.shamrock.deployment.builditem.GeneratedClassBuildItem;
Expand All @@ -59,19 +54,10 @@
import org.jboss.shamrock.restclient.runtime.RestClientProxy;

class RestClientProcessor {

private static final DotName REST_CLIENT = DotName.createSimple(RestClient.class.getName());

private static final DotName[] CLIENT_ANNOTATIONS = {
DotName.createSimple("javax.ws.rs.GET"),
DotName.createSimple("javax.ws.rs.HEAD"),
DotName.createSimple("javax.ws.rs.DELETE"),
DotName.createSimple("javax.ws.rs.OPTIONS"),
DotName.createSimple("javax.ws.rs.PATCH"),
DotName.createSimple("javax.ws.rs.POST"),
DotName.createSimple("javax.ws.rs.PUT"),
DotName.createSimple("javax.ws.rs.PUT"),
DotName.createSimple(RegisterRestClient.class.getName()),
DotName.createSimple(Path.class.getName())
};
private static final DotName REGISTER_REST_CLIENT = DotName.createSimple(RegisterRestClient.class.getName());

@Inject
BuildProducer<GeneratedClassBuildItem> generatedClass;
Expand All @@ -88,11 +74,17 @@ class RestClientProcessor {
@Inject
BuildProducer<SubstrateResourceBuildItem> resources;

@Inject
BuildProducer<BeanRegistrarBuildItem> beanRegistrars;

@Inject
BuildProducer<FeatureBuildItem> feature;

@Inject
CombinedIndexBuildItem combinedIndexBuildItem;

@BuildStep
public void build(BuildProducer<GeneratedBeanBuildItem> generatedBeans, BuildProducer<FeatureBuildItem> feature) throws Exception {
public void build() throws Exception {
feature.produce(new FeatureBuildItem(FeatureBuildItem.MP_REST_CLIENT));
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false,
DefaultResponseExceptionMapper.class.getName(),
Expand All @@ -109,57 +101,58 @@ public void build(BuildProducer<GeneratedBeanBuildItem> generatedBeans, BuildPro
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"));
reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, "org.jboss.resteasy.plugins.providers.jsonb.JsonBindingProvider", "org.jboss.resteasy.plugins.providers.jsonb.AbstractJsonBindingProvider"));
proxyDefinition.produce(new SubstrateProxyDefinitionBuildItem(ResteasyConfiguration.class.getName()));

// According to the spec only rest client interfaces annotated with RegisterRestClient are registered as beans
Map<DotName, ClassInfo> interfaces = new HashMap<>();
for (DotName type : CLIENT_ANNOTATIONS) {
for (AnnotationInstance annotation : combinedIndexBuildItem.getIndex().getAnnotations(type)) {
AnnotationTarget target = annotation.target();
ClassInfo theInfo;
if (target.kind() == AnnotationTarget.Kind.CLASS) {
theInfo = target.asClass();
} else if (target.kind() == AnnotationTarget.Kind.METHOD) {
theInfo = target.asMethod().declaringClass();
} else {
continue;
}
if (!Modifier.isInterface(theInfo.flags())) {
continue;
}
interfaces.put(theInfo.name(), theInfo);
for (AnnotationInstance annotation : combinedIndexBuildItem.getIndex().getAnnotations(REGISTER_REST_CLIENT)) {
AnnotationTarget target = annotation.target();
ClassInfo theInfo;
if (target.kind() == AnnotationTarget.Kind.CLASS) {
theInfo = target.asClass();
} else if (target.kind() == AnnotationTarget.Kind.METHOD) {
theInfo = target.asMethod().declaringClass();
} else {
continue;
}
if (!Modifier.isInterface(theInfo.flags())) {
continue;
}
interfaces.put(theInfo.name(), theInfo);
}

if (interfaces.isEmpty()) {
return;
}

for (Map.Entry<DotName, ClassInfo> entry : interfaces.entrySet()) {
String iName = entry.getKey().toString();
proxyDefinition.produce(new SubstrateProxyDefinitionBuildItem(iName, ResteasyClientProxy.class.getName()));
proxyDefinition.produce(new SubstrateProxyDefinitionBuildItem(iName, RestClientProxy.class.getName()));
reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, iName));

//now generate CDI beans
//TODO: do we need to check if CDI is enabled? Are we just assuming it always is?
String className = iName + "$$RestClientProxy";
AtomicReference<byte[]> bytes = new AtomicReference<>();
try (ClassCreator creator = new ClassCreator(new ClassOutput() {
@Override
public void write(String name, byte[] data) {
bytes.set(data);
generatedClass.produce(new GeneratedClassBuildItem(true, name, data));
}

BeanRegistrar beanRegistrar = new BeanRegistrar() {

@Override
public void register(RegistrationContext registrationContext) {
for (Map.Entry<DotName, ClassInfo> entry : interfaces.entrySet()) {
BeanConfigurator<Object> configurator = registrationContext.configure(entry.getKey());
// The spec is not clear whether we should add superinterfaces too - let's keep aligned with SmallRye for now
configurator.addType(entry.getKey());
// We use @Singleton here as we do not need another proxy
configurator.scope(ScopeInfo.SINGLETON);
configurator.addQualifier(REST_CLIENT);
configurator.creator(m -> {
// return new RestClientBase(proxyType).create();
ResultHandle interfaceHandle = m.loadClass(entry.getKey().toString());
ResultHandle baseHandle = m.newInstance(MethodDescriptor.ofConstructor(RestClientBase.class, Class.class), interfaceHandle);
ResultHandle ret = m.invokeVirtualMethod(MethodDescriptor.ofMethod(RestClientBase.class, "create", Object.class), baseHandle);
m.returnValue(ret);
});
configurator.done();
}
}, className, null, RestClientBase.class.getName())) {

creator.addAnnotation(Dependent.class);
MethodCreator producer = creator.getMethodCreator("producerMethod", iName);
producer.addAnnotation(Produces.class);
producer.addAnnotation(RestClient.class);
producer.addAnnotation(ApplicationScoped.class);

ResultHandle ret = producer.invokeVirtualMethod(MethodDescriptor.ofMethod(RestClientBase.class, "create", Object.class), producer.getThis());
producer.returnValue(ret);

MethodCreator ctor = creator.getMethodCreator(MethodDescriptor.ofConstructor(className));
ctor.invokeSpecialMethod(MethodDescriptor.ofConstructor(RestClientBase.class, Class.class), ctor.getThis(), ctor.loadClass(iName));
ctor.returnValue(null);
}
generatedBeans.produce(new GeneratedBeanBuildItem(className, bytes.get()));
}
};
beanRegistrars.produce(new BeanRegistrarBuildItem(beanRegistrar));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;

import javax.enterprise.context.spi.CreationalContext;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Type;
Expand Down Expand Up @@ -70,18 +72,14 @@ public final class BeanConfigurator<T> {

/**
*
* @param implClass
* @param implClassName
* @param beanDeployment
* @param beanConsumer
*/
BeanConfigurator(Class<?> implClass, BeanDeployment beanDeployment, Consumer<BeanInfo> beanConsumer) {
BeanConfigurator(DotName implClassName, BeanDeployment beanDeployment, Consumer<BeanInfo> beanConsumer) {
this.implClass = beanDeployment.getIndex().getClassByName(Objects.requireNonNull(implClassName));
this.beanDeployment = beanDeployment;
this.beanConsumer = beanConsumer;
this.implClass = beanDeployment.getIndex().getClassByName(DotName.createSimple(implClass.getName()));
if (this.implClass == null) {
// TODO we have a problem
throw new IllegalArgumentException();
}
this.types = new HashSet<>();
this.qualifiers = new HashSet<>();
this.scope = ScopeInfo.DEPENDENT;
Expand Down Expand Up @@ -132,6 +130,16 @@ public BeanConfigurator<T> types(Type... types) {
Collections.addAll(this.types, types);
return this;
}

public BeanConfigurator<T> addType(DotName className) {
this.types.add(Type.create(className, Kind.CLASS));
return this;
}

public BeanConfigurator<T> addQualifier(DotName annotationName) {
this.qualifiers.add(AnnotationInstance.create(annotationName, null, new AnnotationValue[] {}));
return this;
}

public BeanConfigurator<T> qualifiers(AnnotationInstance... qualifiers) {
Collections.addAll(this.qualifiers, qualifiers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ public class BeanDeployment {
RegistrationContext registrationContext = new RegistrationContext() {

@Override
public <T> BeanConfigurator<T> configure(Class<?> beanClass) {
return new BeanConfigurator<T>(beanClass, BeanDeployment.this, beans::add);
public <T> BeanConfigurator<T> configure(DotName beanClassName) {
return new BeanConfigurator<T>(beanClassName, BeanDeployment.this, beans::add);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,13 @@ Collection<Resource> generateSyntheticBean(BeanInfo bean, ReflectionRegistration
} else {
baseName = DotNames.simpleName(bean.getImplClazz().name());
}
baseName += SYNTHETIC_SUFFIX;

Type providerType = bean.getProviderType();
ClassInfo providerClass = bean.getDeployment().getIndex().getClassByName(providerType.name());
String providerTypeName = providerClass.name().toString();
String targetPackage = getPackageName(bean);
String generatedName = targetPackage.replace('.', '/') + "/" + baseName + SYNTHETIC_SUFFIX + BEAN_SUFFIX;
String generatedName = targetPackage.replace('.', '/') + "/" + baseName + BEAN_SUFFIX;

boolean isApplicationClass = applicationClassPredicate.test(bean.getImplClazz().name());
ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, name -> name.equals(generatedName) ? SpecialType.BEAN : null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.jboss.protean.arc.processor;

import org.jboss.jandex.DotName;
import org.jboss.protean.arc.InjectableBean;

/**
Expand All @@ -31,15 +32,18 @@ public interface BeanRegistrar extends BuildExtension {
*/
void register(RegistrationContext registrationContext);


interface RegistrationContext extends BuildContext {

/**
*
* @param beanClass
* @return a new synthetic bean builder
*/
<T> BeanConfigurator<T> configure(Class<?> beanClass);
<T> BeanConfigurator<T> configure(DotName beanClassName);

default <T> BeanConfigurator<T> configure(Class<?> beanClass) {
return configure(DotName.createSimple(beanClass.getName()));
}

// TODO add synthetic observer?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ Collection<MethodInfo> getDelegatingMethods(BeanInfo bean) {
resolved = Types.buildResolvedMap(producerField.type().asParameterizedType().arguments(), fieldClass.typeParameters(), Collections.emptyMap());
}
Methods.addDelegatingMethods(bean.getDeployment().getIndex(), fieldClass, resolved, methods);
} else if (bean.isSynthetic()) {
Methods.addDelegatingMethods(bean.getDeployment().getIndex(), bean.getImplClazz(), Collections.emptyMap(), methods);
}
return methods.values();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,16 @@

package org.jboss.protean.arc;

import java.lang.annotation.Annotation;
import java.util.Set;

import javax.enterprise.inject.spi.InterceptionType;
import javax.interceptor.InvocationContext;
import javax.enterprise.inject.spi.Interceptor;

/**
* Represents an injectable interceptor bean. It is an alternative to {@link javax.enterprise.inject.spi.Interceptor}.
* Represents an interceptor bean.
*
* @author Martin Kouba
*
* @param <T>
*/
public interface InjectableInterceptor<T> extends InjectableBean<T> {

/**
*
* @return the interceptor bindings
*/
Set<Annotation> getInterceptorBindings();

/**
*
* @param type
* @return {@code true} if this interceptor intercepts the given kind of interception, {@code false} othewise
*/
boolean intercepts(InterceptionType type);

/**
*
* @param type
* @param instance
* @param ctx
* @return the invocation return value
* @throws Exception
*/
Object intercept(InterceptionType type, T instance, InvocationContext ctx) throws Exception;
public interface InjectableInterceptor<T> extends InjectableBean<T>, Interceptor<T> {

/**
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public class ClientResource {
@GET
@Path("/manual")
public String manual() throws Exception {

RestInterface iface = RestClientBuilder.newBuilder()
.baseUrl(new URL("http", "localhost", 8080, "/"))
.build(RestInterface.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@RegisterRestClient
@Path("/test")
public interface RestInterface {

Expand Down