Add support for microprofile rest client

This commit is contained in:
Stuart Douglas
2018-09-05 15:59:33 +10:00
parent e41aacfadd
commit a8a2c6624d
36 changed files with 1624 additions and 16 deletions

View File

@@ -1,11 +1,13 @@
package org.jboss.shamrock.arc.deployment;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@@ -60,15 +62,18 @@ public class ArcAnnotationProcessor implements ResourceProcessor {
// Index bean classes registered by shamrock
Indexer indexer = new Indexer();
Set<DotName> additionalIndex = new HashSet<>();
for (Class<?> beanClass : beanDeployment.getAdditionalBeans()) {
for (String beanClass : beanDeployment.getAdditionalBeans()) {
indexBeanClass(beanClass, indexer, beanArchiveIndex.getIndex(), additionalIndex);
}
CompositeIndex index = CompositeIndex.create(indexer.complete(), beanArchiveIndex.getIndex());
Set<String> frameworkPackages = additionalIndex.stream().map(dotName -> {
String name = dotName.toString();
return name.toString().substring(0, name.lastIndexOf("."));
return name.substring(0, name.lastIndexOf("."));
}).collect(Collectors.toSet());
for (Map.Entry<String, byte[]> beanClass : beanDeployment.getGeneratedBeans().entrySet()) {
indexBeanClass(beanClass.getKey(), indexer, beanArchiveIndex.getIndex(), additionalIndex, beanClass.getValue());
}
CompositeIndex index = CompositeIndex.create(indexer.complete(), beanArchiveIndex.getIndex());
Builder builder = BeanProcessor.builder();
builder.setIndex(index);
builder.setAdditionalBeanDefiningAnnotations(additionalBeanDefiningAnnotations);
@@ -126,15 +131,15 @@ public class ArcAnnotationProcessor implements ResourceProcessor {
return RuntimePriority.ARC_DEPLOYMENT;
}
private void indexBeanClass(Class<?> beanClass, Indexer indexer, IndexView shamrockIndex, Set<DotName> additionalIndex) {
DotName beanClassName = DotName.createSimple(beanClass.getName());
private void indexBeanClass(String beanClass, Indexer indexer, IndexView shamrockIndex, Set<DotName> additionalIndex) {
DotName beanClassName = DotName.createSimple(beanClass);
if (additionalIndex.contains(beanClassName)) {
return;
}
ClassInfo beanInfo = shamrockIndex.getClassByName(beanClassName);
if (beanInfo == null) {
System.out.println("Index bean class: " + beanClass);
try (InputStream stream = ArcAnnotationProcessor.class.getClassLoader().getResourceAsStream(beanClass.getName().replace('.', '/') + ".class")) {
try (InputStream stream = ArcAnnotationProcessor.class.getClassLoader().getResourceAsStream(beanClass.replace('.', '/') + ".class")) {
beanInfo = indexer.index(stream);
additionalIndex.add(beanInfo.name());
} catch (IOException e) {
@@ -158,4 +163,35 @@ public class ArcAnnotationProcessor implements ResourceProcessor {
}
}
private void indexBeanClass(String beanClass, Indexer indexer, IndexView shamrockIndex, Set<DotName> additionalIndex, byte[] beanData) {
DotName beanClassName = DotName.createSimple(beanClass);
if (additionalIndex.contains(beanClassName)) {
return;
}
ClassInfo beanInfo = shamrockIndex.getClassByName(beanClassName);
if (beanInfo == null) {
System.out.println("Index bean class: " + beanClass);
try (InputStream stream = new ByteArrayInputStream(beanData)) {
beanInfo = indexer.index(stream);
additionalIndex.add(beanInfo.name());
} catch (IOException e) {
throw new IllegalStateException("Failed to index: " + beanClass);
}
} else {
// The class could be indexed by shamrock - we still need to distinguish framework classes
additionalIndex.add(beanClassName);
}
for (DotName annotationName : beanInfo.annotations().keySet()) {
if (!additionalIndex.contains(annotationName) && shamrockIndex.getClassByName(annotationName) == null) {
try (InputStream annotationStream = ArcAnnotationProcessor.class.getClassLoader()
.getResourceAsStream(annotationName.toString().replace('.', '/') + ".class")) {
System.out.println("Index annotation: " + annotationName);
indexer.index(annotationStream);
additionalIndex.add(annotationName);
} catch (IOException e) {
throw new IllegalStateException("Failed to index: " + beanClass);
}
}
}
}
}

View File

@@ -2,18 +2,32 @@ package org.jboss.shamrock.deployment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class BeanDeployment {
private final List<Class<?>> additionalBeans = new ArrayList<>();
private final List<String> additionalBeans = new ArrayList<>();
private final Map<String, byte[]> generatedBeans = new HashMap<>();
public void addAdditionalBean(Class<?> ... beanClass) {
additionalBeans.addAll(Arrays.asList(beanClass));
public void addAdditionalBean(Class<?>... beanClass) {
additionalBeans.addAll(Arrays.stream(beanClass).map(Class::getName).collect(Collectors.toList()));
}
public List<Class<?>> getAdditionalBeans() {
public void addAdditionalBean(String... beanClass) {
additionalBeans.addAll(Arrays.stream(beanClass).collect(Collectors.toList()));
}
public void addGeneratedBean(String name, byte[] bean) {
generatedBeans.put(name, bean);
}
public List<String> getAdditionalBeans() {
return additionalBeans;
}
public Map<String, byte[]> getGeneratedBeans() {
return generatedBeans;
}
}

View File

@@ -26,8 +26,8 @@ import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -37,7 +37,6 @@ import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.protean.gizmo.BytecodeCreator;
import org.jboss.protean.gizmo.CatchBlockCreator;
import org.jboss.protean.gizmo.ClassCreator;
import org.jboss.protean.gizmo.ExceptionTable;
@@ -133,6 +132,7 @@ public class BuildTimeGenerator {
ArchiveContext context = new ArchiveContextImpl(new ApplicationArchiveImpl(appIndex, root, null), applicationArchives, config);
ProcessorContextImpl processorContext = new ProcessorContextImpl();
processorContext.addResource("META-INF/microprofile-config.properties");
try {
for (ResourceProcessor processor : processors) {
try {
@@ -169,6 +169,7 @@ public class BuildTimeGenerator {
private final Set<String> resources = new HashSet<>();
private final Set<String> resourceBundles = new HashSet<>();
private final Set<String> runtimeInitializedClasses = new HashSet<>();
private final Set<List<String>> proxyClasses = new HashSet<>();
@Override
public BytecodeRecorder addStaticInitTask(int priority) {
@@ -256,6 +257,12 @@ public class BuildTimeGenerator {
runtimeInitializedClasses.addAll(Arrays.asList(classes));
}
@Override
public void addProxyDefinition(String... proxyClasses) {
this.proxyClasses.add(Arrays.asList(proxyClasses));
}
void writeMainClass() throws IOException {
Collections.sort(tasks);
@@ -313,7 +320,7 @@ public class BuildTimeGenerator {
//TODO: at some point we are going to need to break this up, as if it get too big it will hit the method size limit
if(!runtimeInitializedClasses.isEmpty()) {
if (!runtimeInitializedClasses.isEmpty()) {
ExceptionTable tc = beforeAn.addTryCatch();
ResultHandle array = beforeAn.newArray(Class.class, beforeAn.load(runtimeInitializedClasses.size()));
int count = 0;
@@ -330,6 +337,21 @@ public class BuildTimeGenerator {
tc.complete();
}
if (!proxyClasses.isEmpty()) {
ResultHandle proxySupportClass = beforeAn.loadClass("com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry");
ResultHandle proxySupport = beforeAn.invokeStaticMethod(ofMethod("org.graalvm.nativeimage.ImageSingletons", "lookup", Object.class, Class.class), proxySupportClass);
for (List<String> proxy : proxyClasses) {
ResultHandle array = beforeAn.newArray(Class.class, beforeAn.load(proxy.size()));
int i = 0;
for (String p : proxy) {
ResultHandle clazz = beforeAn.invokeStaticMethod(ofMethod(Class.class, "forName", Class.class, String.class), beforeAn.load(p));
beforeAn.writeArrayValue(array, beforeAn.load(i++), clazz);
}
beforeAn.invokeInterfaceMethod(ofMethod("com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry", "addProxyClass", void.class, Class[].class), proxySupport, array);
}
}
for (String i : resources) {
beforeAn.invokeStaticMethod(ofMethod(ResourceHelper.class, "registerResources", void.class, String.class), beforeAn.load(i));
}

View File

@@ -90,4 +90,6 @@ public interface ProcessorContext {
void addResourceBundle(String bundle);
void addRuntimeInitializedClasses(String ... classes);
void addProxyDefinition(String ... proxyClasses);
}

View File

@@ -86,6 +86,11 @@
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.shamrock</groupId>
<artifactId>shamrock-rest-client-deployment</artifactId>
<scope>provided</scope>
</dependency>
<!-- test dependencies -->
@@ -162,6 +167,7 @@
</goals>
<configuration>
<cleanupServer>true</cleanupServer>
<enableHttpUrlHandler>true</enableHttpUrlHandler>
</configuration>
</execution>
</executions>

View File

@@ -0,0 +1,35 @@
package org.jboss.shamrock.example.rest;
import java.net.URL;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Path("/client")
public class ClientResource {
@Inject
@RestClient
private RestInterface restInterface;
@GET
@Path("/manual")
public String manual() throws Exception {
RestInterface iface = RestClientBuilder.newBuilder()
.baseUrl(new URL("http", "localhost", 8080, "/rest"))
.build(RestInterface.class);
return iface.get();
}
@GET
@Path("/cdi")
public String cdi() throws Exception {
return restInterface.get();
}
}

View File

@@ -0,0 +1,12 @@
package org.jboss.shamrock.example.rest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@Path("/test")
public interface RestInterface {
@GET
String get();
}

View File

@@ -1,5 +1,7 @@
package org.jboss.shamrock.example.rest;
import java.net.URL;
import javax.json.Json;
import javax.json.JsonObject;
import javax.ws.rs.GET;
@@ -8,6 +10,8 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import io.reactivex.Single;
@Path("/test")

View File

@@ -0,0 +1 @@
org.jboss.shamrock.example.rest.RestInterface/mp-rest/url=http://localhost:8080/rest

View File

@@ -0,0 +1,9 @@
package org.jboss.shamrock.example.test;
import org.jboss.shamrock.junit.GraalTest;
import org.junit.runner.RunWith;
@RunWith(GraalTest.class)
public class RestClientITCase extends RestClientTestCase {
}

View File

@@ -0,0 +1,44 @@
package org.jboss.shamrock.example.test;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import org.jboss.shamrock.junit.ShamrockTest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(ShamrockTest.class)
public class RestClientTestCase {
@Test
public void testMicroprofileClient() throws Exception {
URL uri = new URL("http://localhost:8080/rest/client/manual");
URLConnection connection = uri.openConnection();
InputStream in = connection.getInputStream();
byte[] buf = new byte[100];
int r;
ByteArrayOutputStream out = new ByteArrayOutputStream();
while ((r = in.read(buf)) > 0) {
out.write(buf, 0, r);
}
Assert.assertEquals("TEST", new String(out.toByteArray()));
}
@Test
public void testMicroprofileClientCDIIntegration() throws Exception {
URL uri = new URL("http://localhost:8080/rest/client/cdi");
URLConnection connection = uri.openConnection();
InputStream in = connection.getInputStream();
byte[] buf = new byte[100];
int r;
ByteArrayOutputStream out = new ByteArrayOutputStream();
while ((r = in.read(buf)) > 0) {
out.write(buf, 0, r);
}
Assert.assertEquals("TEST", new String(out.toByteArray()));
}
}

View File

@@ -1,9 +1,37 @@
package org.jboss.protean.gizmo;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
public interface AnnotatedElement {
AnnotationCreator addAnnotation(String annotationType);
AnnotationCreator addAnnotation(Class<?> annotationType);
default void addAnnotation(AnnotationInstance annotation) {
AnnotationCreator ac = addAnnotation(annotation.name().toString());
for (AnnotationValue member : annotation.values()) {
if (member.kind() == AnnotationValue.Kind.NESTED || member.kind() == AnnotationValue.Kind.ARRAY) {
throw new RuntimeException("Not Yet Implemented: Cannot generate annotation " + annotation);
} else if (member.kind() == AnnotationValue.Kind.BOOLEAN) {
ac.addValue(member.name(), member.asBoolean());
} else if (member.kind() == AnnotationValue.Kind.BYTE) {
ac.addValue(member.name(), member.asByte());
} else if (member.kind() == AnnotationValue.Kind.SHORT) {
ac.addValue(member.name(), member.asShort());
} else if (member.kind() == AnnotationValue.Kind.INTEGER) {
ac.addValue(member.name(), member.asInt());
} else if (member.kind() == AnnotationValue.Kind.LONG) {
ac.addValue(member.name(), member.asLong());
} else if (member.kind() == AnnotationValue.Kind.FLOAT) {
ac.addValue(member.name(), member.asFloat());
} else if (member.kind() == AnnotationValue.Kind.DOUBLE) {
ac.addValue(member.name(), member.asDouble());
} else if (member.kind() == AnnotationValue.Kind.STRING) {
ac.addValue(member.name(), member.asString());
}
}
}
}

View File

@@ -13,6 +13,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

View File

@@ -5,7 +5,7 @@ import java.util.List;
/**
* A class that builds the body of a method without needing to understand java bytecode.
*/
public interface MethodCreator extends MemberCreator<MethodCreator>, BytecodeCreator {
public interface MethodCreator extends MemberCreator<MethodCreator>, BytecodeCreator, AnnotatedElement {
/**
* Adds an exception to the method signature

View File

@@ -4,8 +4,10 @@ import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -14,6 +16,7 @@ class MethodCreatorImpl extends BytecodeCreatorImpl implements MethodCreator {
private int modifiers = Opcodes.ACC_PUBLIC;
private final List<String> exceptions = new ArrayList<>();
private final List<AnnotationCreatorImpl> annotations = new ArrayList<>();
MethodCreatorImpl(MethodDescriptor methodDescriptor, String declaringClassName, ClassOutput classOutput, ClassCreator classCreator) {
super(methodDescriptor, declaringClassName, classOutput, classCreator);
@@ -62,6 +65,14 @@ class MethodCreatorImpl extends BytecodeCreatorImpl implements MethodCreator {
int varCount = allocateLocalVariables(localVarCount);
writeOperations(visitor);
visitor.visitMaxs(0, varCount);
for(AnnotationCreatorImpl annotation : annotations) {
AnnotationVisitor av = visitor.visitAnnotation(DescriptorUtils.extToInt(annotation.getAnnotationType()), true);
for(Map.Entry<String, Object> e : annotation.getValues().entrySet()) {
av.visit(e.getKey(), e.getValue());
}
av.visitEnd();
}
visitor.visitEnd();
}
@@ -69,4 +80,16 @@ class MethodCreatorImpl extends BytecodeCreatorImpl implements MethodCreator {
public String toString() {
return "MethodCreatorImpl [declaringClassName=" + declaringClassName + ", methodDescriptor=" + methodDescriptor + "]";
}
@Override
public AnnotationCreator addAnnotation(String annotationType) {
AnnotationCreatorImpl ac = new AnnotationCreatorImpl(annotationType);
annotations.add(ac);
return ac;
}
@Override
public AnnotationCreator addAnnotation(Class<?> annotationType) {
return addAnnotation(annotationType.getName());
}
}

View File

@@ -25,6 +25,10 @@
<groupId>org.jboss.shamrock</groupId>
<artifactId>shamrock-jaxrs-runtime</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.graalvm</groupId>
<artifactId>graal-annotations</artifactId>
</dependency>
</dependencies>

View File

@@ -44,6 +44,8 @@ public class NativeImageMojo extends AbstractMojo {
@Parameter(defaultValue = "${native-image.new-server}")
private boolean cleanupServer;
@Parameter
private boolean enableHttpUrlHandler;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
@@ -75,6 +77,8 @@ public class NativeImageMojo extends AbstractMojo {
}
command.add("-jar");
command.add(finalName + "-runner.jar");
//https://github.com/oracle/graal/issues/660
command.add("-J-Djava.util.concurrent.ForkJoinPool.common.parallelism=1");
if (reportErrorsAtRuntime) {
command.add("-H:+ReportUnsupportedElementsAtRuntime");
}
@@ -87,6 +91,9 @@ public class NativeImageMojo extends AbstractMojo {
command.add("-J-Djava.compiler=NONE");
command.add("-J-Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=y");
}
if(enableHttpUrlHandler) {
command.add("-H:EnableURLProtocols=http");
}
//command.add("-H:+AllowVMInspection");
System.out.println(command);
Process process = Runtime.getRuntime().exec(command.toArray(new String[0]), null, outputDirectory);

22
pom.xml
View File

@@ -56,6 +56,7 @@
<jboss-transaction-spi.version>7.6.0.Final</jboss-transaction-spi.version>
<javax.persistence-api.version>2.2</javax.persistence-api.version>
<rxjava.version>2.1.12</rxjava.version>
<microprofile-rest-client-api.version>1.0</microprofile-rest-client-api.version>
</properties>
<modules>
@@ -76,6 +77,7 @@
<module>bean-validation</module>
<module>transactions</module>
<module>agroal</module>
<module>rest-client</module>
</modules>
<build>
<pluginManagement>
@@ -211,6 +213,16 @@
<artifactId>shamrock-junit</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.shamrock</groupId>
<artifactId>shamrock-rest-client-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.shamrock</groupId>
<artifactId>shamrock-rest-client-runtime</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.shamrock</groupId>
<artifactId>shamrock-shared-library-example</artifactId>
@@ -422,6 +434,11 @@
<artifactId>microprofile-config-api</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.rest.client</groupId>
<artifactId>microprofile-rest-client-api</artifactId>
<version>${microprofile-rest-client-api.version}</version>
</dependency>
<dependency>
<groupId>org.fakereplace</groupId>
<artifactId>fakereplace</artifactId>
@@ -490,6 +507,11 @@
<artifactId>resteasy-cdi</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>shamrock-rest-client</artifactId>
<groupId>org.jboss.shamrock</groupId>
<version>1.0.0.Alpha1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shamrock-rest-client-deployment</artifactId>
<dependencies>
<dependency>
<groupId>org.jboss.shamrock</groupId>
<artifactId>shamrock-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.shamrock</groupId>
<artifactId>shamrock-rest-client-runtime</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,141 @@
package org.jboss.shamrock.restclient;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Default;
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;
import org.apache.commons.logging.impl.Jdk14Logger;
import org.apache.commons.logging.impl.LogFactoryImpl;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.jandex.AnnotationInstance;
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.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.ArchiveContext;
import org.jboss.shamrock.deployment.BeanDeployment;
import org.jboss.shamrock.deployment.ProcessorContext;
import org.jboss.shamrock.deployment.ResourceProcessor;
import org.jboss.shamrock.deployment.ShamrockConfig;
import org.jboss.shamrock.restclient.runtime.DefaultResponseExceptionMapper;
import org.jboss.shamrock.restclient.runtime.RestClientBase;
import org.jboss.shamrock.restclient.runtime.RestClientProxy;
class RestClientProcessor implements ResourceProcessor {
private static final Logger log = Logger.getLogger(RestClientProxy.class.getName());
private static final DotName REGISTER_REST_CLIENT = DotName.createSimple(RegisterRestClient.class.getName());
@Inject
private BeanDeployment beanDeployment;
@Inject
private ShamrockConfig config;
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())
};
@Override
public void process(ArchiveContext archiveContext, ProcessorContext processorContext) throws Exception {
processorContext.addReflectiveClass(false, false,
DefaultResponseExceptionMapper.class.getName(),
LogFactoryImpl.class.getName(),
Jdk14Logger.class.getName());
processorContext.addReflectiveClass(false, false, ClientRequestFilter[].class.getName());
processorContext.addReflectiveClass(false, false, ClientResponseFilter[].class.getName());
beanDeployment.addAdditionalBean(RestClient.class);
processorContext.addResource("META-INF/services/javax.ws.rs.ext.Providers");
//TODO: fix this, we don't want to just add all the providers
processorContext.addReflectiveClass(false, false, "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
processorContext.addReflectiveClass(false, false, "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
processorContext.addProxyDefinition(ResteasyConfiguration.class.getName());
Map<DotName, ClassInfo> interfaces = new HashMap<>();
for (DotName type : CLIENT_ANNOTATIONS) {
for (AnnotationInstance annotation : archiveContext.getCombinedIndex().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 (Map.Entry<DotName, ClassInfo> entry : interfaces.entrySet()) {
String iName = entry.getKey().toString();
processorContext.addProxyDefinition(iName, ResteasyClientProxy.class.getName());
processorContext.addProxyDefinition(iName, RestClientProxy.class.getName());
processorContext.addReflectiveClass(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) {
try {
bytes.set(data);
processorContext.addGeneratedClass(true, name, data);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}, className, null, RestClientBase.class.getName())) {
creator.addAnnotation(ApplicationScoped.class);
MethodCreator producer = creator.getMethodCreator("producerMethod", iName);
producer.addAnnotation(Produces.class);
producer.addAnnotation(RestClient.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);
}
beanDeployment.addGeneratedBean(className, bytes.get());
}
}
@Override
public int getPriority() {
return 1;
}
}

View File

@@ -0,0 +1,11 @@
package org.jboss.shamrock.restclient;
import org.jboss.shamrock.deployment.SetupContext;
import org.jboss.shamrock.deployment.ShamrockSetup;
public class RestClientSetup implements ShamrockSetup {
@Override
public void setup(SetupContext context) {
context.addResourceProcessor(new RestClientProcessor());
}
}

View File

@@ -0,0 +1 @@
org.jboss.shamrock.restclient.RestClientSetup

18
rest-client/pom.xml Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>shamrock-parent</artifactId>
<groupId>org.jboss.shamrock</groupId>
<version>1.0.0.Alpha1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shamrock-rest-client</artifactId>
<packaging>pom</packaging>
<modules>
<module>deployment</module>
<module>runtime</module>
</modules>
</project>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>shamrock-rest-client</artifactId>
<groupId>org.jboss.shamrock</groupId>
<version>1.0.0.Alpha1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shamrock-rest-client-runtime</artifactId>
<dependencies>
<dependency>
<groupId>org.jboss.shamrock</groupId>
<artifactId>shamrock-core-runtime</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.rest.client</groupId>
<artifactId>microprofile-rest-client-api</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.graalvm</groupId>
<artifactId>graal-annotations</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,29 @@
/**
* Copyright 2015-2017 Red Hat, Inc, and individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.shamrock.restclient.runtime;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver;
/**
* Created by hbraun on 22.01.18.
*/
public class BuilderResolver extends RestClientBuilderResolver {
@Override
public RestClientBuilder newBuilder() {
return new RestClientBuilderImpl();
}
}

View File

@@ -0,0 +1,110 @@
/**
* Copyright 2015-2017 Red Hat, Inc, and individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.shamrock.restclient.runtime;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Feature;
/**
* Created by hbraun on 22.01.18.
*/
class ConfigurationWrapper implements Configuration {
public ConfigurationWrapper(Configuration delegate) {
this.delegate = delegate;
}
@Override
public RuntimeType getRuntimeType() {
return delegate.getRuntimeType();
}
@Override
public Map<String, Object> getProperties() {
return delegate.getProperties();
}
@Override
public Object getProperty(String name) {
return delegate.getProperty(name);
}
@Override
public Collection<String> getPropertyNames() {
return delegate.getPropertyNames();
}
@Override
public boolean isEnabled(Feature feature) {
return delegate.isEnabled(feature);
}
@Override
public boolean isEnabled(Class<? extends Feature> featureClass) {
return delegate.isEnabled(featureClass);
}
@Override
public boolean isRegistered(Object component) {
return delegate.isRegistered(component);
}
@Override
public boolean isRegistered(Class<?> componentClass) {
return delegate.isRegistered(componentClass);
}
@Override
public Map<Class<?>, Integer> getContracts(Class<?> componentClass) {
Map<Class<?>, Integer> contracts = new HashMap<>();
contracts.putAll(getLocalContracts(componentClass));
contracts.putAll(delegate.getContracts(componentClass));
return contracts;
}
private Map<Class<?>, ? extends Integer> getLocalContracts(Class<?> componentClass) {
if (localClassContracts.containsKey(componentClass)) {
return localClassContracts.get(componentClass);
} else {
return Collections.emptyMap();
}
}
@Override
public Set<Class<?>> getClasses() {
return delegate.getClasses();
}
@Override
public Set<Object> getInstances() {
return delegate.getInstances();
}
void registerLocalContract(Class<?> provider, Map<Class<?>, Integer> contracts) {
localClassContracts.put(provider, contracts);
}
protected Map<Class<?>, Map<Class<?>, Integer>> localClassContracts = new HashMap<>();
private final Configuration delegate;
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright 2015-2017 Red Hat, Inc, and individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.shamrock.restclient.runtime;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
/**
* Created by hbraun on 17.01.18.
*/
public class DefaultResponseExceptionMapper implements ResponseExceptionMapper {
@Override
public Throwable toThrowable(Response response) {
return new WebApplicationException("Unknown error, status code " + response.getStatus(), response);
}
@Override
public boolean handles(int status, MultivaluedMap headers) {
return status >= 400;
}
@Override
public int getPriority() {
return Integer.MAX_VALUE;
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright 2015-2017 Red Hat, Inc, and individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.shamrock.restclient.runtime;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
/**
* Created by hbraun on 22.01.18.
*/
class ExceptionMapping implements ClientResponseFilter {
ExceptionMapping(Set<Object> instances) {
this.instances = instances;
}
@Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
Response response = new PartialResponse(responseContext);
Map<ResponseExceptionMapper, Integer> mappers = new HashMap<>();
for (Object o : instances) {
if (o instanceof ResponseExceptionMapper) {
ResponseExceptionMapper candiate = (ResponseExceptionMapper) o;
if (candiate.handles(response.getStatus(), response.getHeaders())) {
mappers.put(candiate, candiate.getPriority());
}
}
}
if (mappers.size() > 0) {
Map<Optional<Throwable>, Integer> errors = new HashMap<>();
mappers.forEach((m, i) -> {
Optional<Throwable> t = Optional.ofNullable(m.toThrowable(response));
errors.put(t, i);
});
Optional<Throwable> prioritised = Optional.empty();
for (Optional<Throwable> throwable : errors.keySet()) {
if (throwable.isPresent()) {
if (!prioritised.isPresent()) {
prioritised = throwable;
} else if (errors.get(throwable) < errors.get(prioritised)) {
prioritised = throwable;
}
}
}
if (prioritised.isPresent()) { // strange rule from the spec
throw (WebApplicationException) prioritised.get();
}
}
}
private Set<Object> instances;
}

View File

@@ -0,0 +1,213 @@
/**
* Copyright 2015-2017 Red Hat, Inc, and individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.shamrock.restclient.runtime;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
/**
* Created by hbraun on 22.01.18.
*/
public class PartialResponse extends Response implements Serializable {
PartialResponse(ClientResponseContext responseContext) {
this.responseContext = responseContext;
}
@Override
public int getStatus() {
return responseContext.getStatus();
}
@Override
public StatusType getStatusInfo() {
return responseContext.getStatusInfo();
}
@Override
public Object getEntity() {
throw notSupported();
}
private RuntimeException notSupported() {
RuntimeException ex = new RuntimeException("method call not supported");
ex.printStackTrace();
return ex;
}
@Override
public <T> T readEntity(Class<T> entityType) {
if (entityType.isAssignableFrom(String.class)) {
return (T) readStringEntity(responseContext.getEntityStream());
} else {
throw notSupported();
}
}
public static String readStringEntity(InputStream input) {
try {
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(input))) {
return buffer.lines().collect(Collectors.joining("\n"));
}
} catch (IOException e) {
throw new WebApplicationException("Failed to read entity", e);
}
}
@Override
public <T> T readEntity(GenericType<T> entityType) {
throw notSupported();
}
@Override
public <T> T readEntity(Class<T> entityType, Annotation[] annotations) {
throw notSupported();
}
@Override
public <T> T readEntity(GenericType<T> entityType, Annotation[] annotations) {
throw notSupported();
}
@Override
public boolean hasEntity() {
return responseContext.hasEntity();
}
@Override
public boolean bufferEntity() {
throw new RuntimeException("method call not supported");
}
@Override
public void close() {
try {
responseContext.getEntityStream().close();
} catch (Throwable e) {
// ignore
}
}
@Override
public MediaType getMediaType() {
return responseContext.getMediaType();
}
@Override
public Locale getLanguage() {
return responseContext.getLanguage();
}
@Override
public int getLength() {
return responseContext.getLength();
}
@Override
public Set<String> getAllowedMethods() {
return responseContext.getAllowedMethods();
}
@Override
public Map<String, NewCookie> getCookies() {
return responseContext.getCookies();
}
@Override
public EntityTag getEntityTag() {
return responseContext.getEntityTag();
}
@Override
public Date getDate() {
return responseContext.getDate();
}
@Override
public Date getLastModified() {
return responseContext.getLastModified();
}
@Override
public URI getLocation() {
return responseContext.getLocation();
}
@Override
public Set<Link> getLinks() {
return responseContext.getLinks();
}
@Override
public boolean hasLink(String relation) {
return responseContext.hasLink(relation);
}
@Override
public Link getLink(String relation) {
return responseContext.getLink(relation);
}
@Override
public Link.Builder getLinkBuilder(String relation) {
throw new RuntimeException("method call not supported");
}
@Override
public MultivaluedMap<String, Object> getMetadata() {
MultivaluedMap<String, Object> metaData = new MultivaluedMapImpl<String, Object>();
// TODO
return metaData;
}
@Override
public MultivaluedMap<String, String> getStringHeaders() {
return responseContext.getHeaders();
}
@Override
public String getHeaderString(String name) {
return responseContext.getHeaderString(name);
}
private final transient ClientResponseContext responseContext;
}

View File

@@ -0,0 +1,37 @@
package org.jboss.shamrock.restclient.runtime;
import java.net.MalformedURLException;
import java.net.URL;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
public class RestClientBase {
public static final String REST_URL_FORMAT = "%s/mp-rest/url";
private final Class<?> proxyType;
private final Config config;
public RestClientBase(Class<?> proxyType) {
this.proxyType = proxyType;
this.config = ConfigProvider.getConfig();
}
public Object create() {
RestClientBuilder builder = RestClientBuilder.newBuilder();
String baseUrl = getBaseUrl();
try {
return builder.baseUrl(new URL(baseUrl)).build(proxyType);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("The value of URL was invalid " + baseUrl);
}
}
private String getBaseUrl() {
String property = String.format(REST_URL_FORMAT, proxyType.getName());
return config.getValue(property, String.class);
}
}

View File

@@ -0,0 +1,508 @@
/**
* Copyright 2015-2017 Red Hat, Inc, and individual contributors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.shamrock.restclient.runtime;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.Provider;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLContextSpi;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Priorities;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.ext.ParamConverterProvider;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.RestClientDefinitionException;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
import org.jboss.logging.Logger;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.specimpl.ResteasyUriBuilder;
/**
* Created by hbraun on 15.01.18.
*/
class RestClientBuilderImpl implements RestClientBuilder {
private static final Logger LOGGER = Logger.getLogger(RestClientBuilderImpl.class);
private static final String RESTEASY_PROPERTY_PREFIX = "resteasy.";
private static final String DEFAULT_MAPPER_PROP = "microprofile.rest.client.disable.default.mapper";
RestClientBuilderImpl() {
ClientBuilder availableBuilder = ClientBuilder.newBuilder();
if (availableBuilder instanceof ResteasyClientBuilder) {
this.builderDelegate = (ResteasyClientBuilder) availableBuilder;
this.configurationWrapper = new ConfigurationWrapper(this.builderDelegate.getConfiguration());
this.config = ConfigProvider.getConfig();
} else {
throw new IllegalStateException("Incompatible client builder found " + availableBuilder.getClass());
}
}
public Configuration getConfigurationWrapper() {
return this.configurationWrapper;
}
@Override
public RestClientBuilder baseUrl(URL url) {
try {
this.baseURI = url.toURI();
return this;
} catch (URISyntaxException e) {
throw new RuntimeException(e.getMessage());
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T build(Class<T> aClass) throws IllegalStateException, RestClientDefinitionException {
// Interface validity
verifyInterface(aClass);
// Provider annotations
Annotation[] providers = aClass.getAnnotations();
for (Annotation provider : providers) {
if(provider instanceof RegisterProvider) {
RegisterProvider p = (RegisterProvider) provider;
register(p.value(), p.priority());
}
}
// Default exception mapper
if (!isMapperDisabled()) {
register(DefaultResponseExceptionMapper.class);
}
this.builderDelegate.register(new ExceptionMapping(localProviderInstances), 1);
ClassLoader classLoader = aClass.getClassLoader();
List<String> noProxyHosts = Arrays.asList(
System.getProperty("http.nonProxyHosts", "localhost|127.*|[::1]").split("|"));
String proxyHost = System.getProperty("http.proxyHost");
T actualClient;
ResteasyClient client;
//TODO: Substrate does not support SSL yet
this.builderDelegate.sslContext(new SSLContext(new SSLContextSpi() {
@Override
protected void engineInit(KeyManager[] keyManagers, TrustManager[] trustManagers, SecureRandom secureRandom) throws KeyManagementException {
}
@Override
protected SSLSocketFactory engineGetSocketFactory() {
return new SSLSocketFactory() {
@Override
public String[] getDefaultCipherSuites() {
return new String[0];
}
@Override
public String[] getSupportedCipherSuites() {
return new String[0];
}
@Override
public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
return null;
}
@Override
public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
return null;
}
@Override
public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
return null;
}
@Override
public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
return null;
}
@Override
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
return null;
}
};
}
@Override
protected SSLServerSocketFactory engineGetServerSocketFactory() {
return null;
}
@Override
protected SSLEngine engineCreateSSLEngine() {
return null;
}
@Override
protected SSLEngine engineCreateSSLEngine(String s, int i) {
return null;
}
@Override
protected SSLSessionContext engineGetServerSessionContext() {
return null;
}
@Override
protected SSLSessionContext engineGetClientSessionContext() {
return null;
}
}, new Provider("Dummy", 1, "Dummy") {
@Override
public String getName() {
return super.getName();
}
}, "BOGUS") {
});
if (proxyHost != null && !noProxyHosts.contains(this.baseURI.getHost())) {
// Use proxy, if defined
client = this.builderDelegate.defaultProxy(
proxyHost,
Integer.parseInt(System.getProperty("http.proxyPort", "80")))
.build();
} else {
client = this.builderDelegate.build();
}
actualClient = client.target(this.baseURI)
.proxyBuilder(aClass)
.classloader(classLoader)
.defaultConsumes(MediaType.TEXT_PLAIN)
.defaultProduces(MediaType.TEXT_PLAIN).build();
Class<?>[] interfaces = new Class<?>[2];
interfaces[0] = aClass;
interfaces[1] = RestClientProxy.class;
return (T) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(actualClient, args);
}
});
}
private boolean isMapperDisabled() {
boolean disabled = false;
Optional<Boolean> defaultMapperProp = this.config.getOptionalValue(DEFAULT_MAPPER_PROP, Boolean.class);
// disabled through config api
if (defaultMapperProp.isPresent() && defaultMapperProp.get().equals(Boolean.TRUE)) {
disabled = true;
} else if (!defaultMapperProp.isPresent()) {
// disabled through jaxrs property
try {
Object property = this.builderDelegate.getConfiguration().getProperty(DEFAULT_MAPPER_PROP);
if (property != null) {
disabled = (Boolean) property;
}
} catch (Throwable e) {
// ignore cast exception
}
}
return disabled;
}
private <T> void verifyInterface(Class<T> typeDef) {
Method[] methods = typeDef.getMethods();
// multiple verbs
for (Method method : methods) {
boolean hasHttpMethod = false;
for (Annotation annotation : method.getAnnotations()) {
boolean isHttpMethod = (annotation.annotationType().getAnnotation(HttpMethod.class) != null);
if (!hasHttpMethod && isHttpMethod) {
hasHttpMethod = true;
} else if (hasHttpMethod && isHttpMethod) {
throw new RestClientDefinitionException("Ambiguous @Httpmethod defintion on type " + typeDef);
}
}
}
// invalid parameter
Path classPathAnno = typeDef.getAnnotation(Path.class);
final Set<String> classLevelVariables = new HashSet<>();
ResteasyUriBuilder classTemplate = null;
if (classPathAnno != null) {
classTemplate = (ResteasyUriBuilder) UriBuilder.fromUri(classPathAnno.value());
classLevelVariables.addAll(classTemplate.getPathParamNamesInDeclarationOrder());
}
ResteasyUriBuilder template;
for (Method method : methods) {
Path methodPathAnno = method.getAnnotation(Path.class);
if (methodPathAnno != null) {
template = classPathAnno == null ? (ResteasyUriBuilder) UriBuilder.fromUri(methodPathAnno.value())
: (ResteasyUriBuilder) UriBuilder.fromUri(classPathAnno.value() + "/" + methodPathAnno.value());
} else {
template = classTemplate;
}
if (template == null) {
continue;
}
// it's not executed, so this can be anything - but a hostname needs to present
template.host("localhost");
Set<String> allVariables = new HashSet<>(template.getPathParamNamesInDeclarationOrder());
Map<String, Object> paramMap = new HashMap<>();
for (Parameter p : method.getParameters()) {
PathParam pathParam = p.getAnnotation(PathParam.class);
if (pathParam != null) {
paramMap.put(pathParam.value(), "foobar");
}
}
if (allVariables.size() != paramMap.size()) {
throw new RestClientDefinitionException("Parameters and variables don't match on " + typeDef + "::" + method.getName());
}
try {
template.resolveTemplates(paramMap, false).build();
} catch (IllegalArgumentException ex) {
throw new RestClientDefinitionException("Parameter names don't match variable names on " + typeDef + "::" + method.getName(), ex);
}
}
}
@Override
public Configuration getConfiguration() {
return getConfigurationWrapper();
}
@Override
public RestClientBuilder property(String name, Object value) {
if (name.startsWith(RESTEASY_PROPERTY_PREFIX)) {
// Allows to configure some of the ResteasyClientBuilder delegate properties
String builderMethodName = name.substring(RESTEASY_PROPERTY_PREFIX.length());
try {
Method builderMethod = ResteasyClientBuilder.class.getMethod(builderMethodName, unwrapPrimitiveType(value));
builderMethod.invoke(builderDelegate, value);
} catch (NoSuchMethodException e) {
LOGGER.warnf("ResteasyClientBuilder method %s not found", builderMethodName);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOGGER.errorf(e, "Unable to invoke ResteasyClientBuilder method %s", builderMethodName);
}
}
this.builderDelegate.property(name, value);
return this;
}
private static Class<?> unwrapPrimitiveType(Object value) {
if (value instanceof Integer) {
return int.class;
} else if (value instanceof Long) {
return long.class;
} else if (value instanceof Boolean) {
return boolean.class;
}
return value.getClass();
}
private static Object newInstanceOf(Class<?> clazz) {
try {
return clazz.newInstance();
} catch (Throwable t) {
throw new RuntimeException("Failed to register " + clazz, t);
}
}
@Override
public RestClientBuilder register(Class<?> aClass) {
this.register(newInstanceOf(aClass));
return this;
}
@Override
public RestClientBuilder register(Class<?> aClass, int i) {
this.register(newInstanceOf(aClass), i);
return this;
}
@Override
public RestClientBuilder register(Class<?> aClass, Class<?>[] classes) {
this.register(newInstanceOf(aClass), classes);
return this;
}
@Override
public RestClientBuilder register(Class<?> aClass, Map<Class<?>, Integer> map) {
this.register(newInstanceOf(aClass), map);
return this;
}
@Override
public RestClientBuilder register(Object o) {
if (o instanceof ResponseExceptionMapper) {
ResponseExceptionMapper mapper = (ResponseExceptionMapper) o;
register(mapper, mapper.getPriority());
} else if (o instanceof ParamConverterProvider) {
register(o, Priorities.USER);
} else {
this.builderDelegate.register(o);
}
return this;
}
@Override
public RestClientBuilder register(Object o, int i) {
if (o instanceof ResponseExceptionMapper) {
// local
ResponseExceptionMapper mapper = (ResponseExceptionMapper) o;
HashMap<Class<?>, Integer> contracts = new HashMap<>();
contracts.put(ResponseExceptionMapper.class, i);
registerLocalProviderInstance(mapper, contracts);
// delegate
this.builderDelegate.register(mapper, i);
} else if (o instanceof ParamConverterProvider) {
// local
ParamConverterProvider converter = (ParamConverterProvider) o;
HashMap<Class<?>, Integer> contracts = new HashMap<>();
contracts.put(ParamConverterProvider.class, i);
registerLocalProviderInstance(converter, contracts);
// delegate
this.builderDelegate.register(converter, i);
} else {
this.builderDelegate.register(o, i);
}
return this;
}
@Override
public RestClientBuilder register(Object o, Class<?>[] classes) {
// local
for (Class<?> aClass : classes) {
if (aClass.isAssignableFrom(ResponseExceptionMapper.class)) {
register(o);
}
}
// other
this.builderDelegate.register(o, classes);
return this;
}
@Override
public RestClientBuilder register(Object o, Map<Class<?>, Integer> map) {
if (o instanceof ResponseExceptionMapper) {
//local
ResponseExceptionMapper mapper = (ResponseExceptionMapper) o;
HashMap<Class<?>, Integer> contracts = new HashMap<>();
contracts.put(ResponseExceptionMapper.class, map.get(ResponseExceptionMapper.class));
registerLocalProviderInstance(mapper, contracts);
// other
this.builderDelegate.register(o, map);
} else {
this.builderDelegate.register(o, map);
}
return this;
}
public Set<Object> getLocalProviderInstances() {
return localProviderInstances;
}
public void registerLocalProviderInstance(Object provider, Map<Class<?>, Integer> contracts) {
for (Object registered : getLocalProviderInstances()) {
if (registered == provider) {
System.out.println("Provider already registered " + provider.getClass().getName());
return;
}
}
localProviderInstances.add(provider);
configurationWrapper.registerLocalContract(provider.getClass(), contracts);
}
private final ResteasyClientBuilder builderDelegate;
private final ConfigurationWrapper configurationWrapper;
private final Config config;
private URI baseURI;
private Set<Object> localProviderInstances = new HashSet<Object>();
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright 2018 Red Hat, Inc, and individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.shamrock.restclient.runtime;
import javax.ws.rs.client.Client;
/**
* This interface is implemented by every proxy created by {@link io.smallrye.restclient.RestClientBuilderImpl}.
*
* @author Martin Kouba
*/
public interface RestClientProxy {
/**
*
* @return the underlying {@link Client} instance
*/
Client getClient();
}

View File

@@ -0,0 +1,17 @@
package org.jboss.shamrock.restclient.runtime.graal;
import javax.ws.rs.client.ClientBuilder;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
@TargetClass(ClientBuilder.class)
final class ClientBuilderReplacement {
@Substitute
public static ClientBuilder newBuilder() {
return new ResteasyClientBuilder();
}
}

View File

@@ -0,0 +1,16 @@
package org.jboss.shamrock.restclient.runtime.graal;
import org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver;
import org.jboss.shamrock.restclient.runtime.BuilderResolver;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
@TargetClass(RestClientBuilderResolver.class)
final class RestClientBuilderResolverReplacement {
@Substitute
private static RestClientBuilderResolver loadSpi(ClassLoader cl) {
return new BuilderResolver();
}
}

View File

@@ -0,0 +1 @@
org.jboss.shamrock.restclient.runtime.BuilderResolver

View File

@@ -38,8 +38,11 @@ public class WeldAnnotationProcessor implements ResourceProcessor {
template.addClass(init, recorder.classProxy(name));
processorContext.addReflectiveClass(true, true, name);
}
for (Class<?> clazz : beanDeployment.getAdditionalBeans()) {
template.addClass(init, clazz);
for (String clazz : beanDeployment.getAdditionalBeans()) {
template.addClass(init, recorder.classProxy(clazz));
}
for (String clazz : beanDeployment.getGeneratedBeans().keySet()) {
template.addClass(init, recorder.classProxy(clazz));
}
SeContainer weld = template.doBoot(null, init);
template.initBeanContainer(weld);