diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java index fbe5166fc48ba..bf3024aa3e880 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java @@ -26,8 +26,8 @@ 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.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; @@ -169,6 +168,7 @@ private final class ProcessorContextImpl implements ProcessorContext { private final Set resources = new HashSet<>(); private final Set resourceBundles = new HashSet<>(); private final Set runtimeInitializedClasses = new HashSet<>(); + private final Set> proxyClasses = new HashSet<>(); @Override public BytecodeRecorder addStaticInitTask(int priority) { @@ -256,6 +256,12 @@ public void addRuntimeInitializedClasses(String... classes) { 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 +319,7 @@ void writeReflectionAutoFeature() throws IOException { //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 +336,21 @@ void writeReflectionAutoFeature() throws IOException { 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 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)); } diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java index b8b80adb034f7..1fdcb1514ddb2 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java @@ -90,4 +90,6 @@ public interface ProcessorContext { void addResourceBundle(String bundle); void addRuntimeInitializedClasses(String ... classes); + + void addProxyDefinition(String ... proxyClasses); } diff --git a/examples/strict/pom.xml b/examples/strict/pom.xml index 17db2fdf66d8f..5f56a12ec1003 100644 --- a/examples/strict/pom.xml +++ b/examples/strict/pom.xml @@ -86,6 +86,11 @@ io.reactivex.rxjava2 rxjava + + org.jboss.shamrock + shamrock-rest-client-deployment + provided + @@ -162,6 +167,7 @@ true + true diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/rest/RestInterface.java b/examples/strict/src/main/java/org/jboss/shamrock/example/rest/RestInterface.java new file mode 100644 index 0000000000000..2e127b07796b4 --- /dev/null +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/rest/RestInterface.java @@ -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(); + +} diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/rest/TestResource.java b/examples/strict/src/main/java/org/jboss/shamrock/example/rest/TestResource.java index c81a17c737549..284fca0475970 100644 --- a/examples/strict/src/main/java/org/jboss/shamrock/example/rest/TestResource.java +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/rest/TestResource.java @@ -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.Produces; import javax.xml.bind.annotation.XmlRootElement; +import org.eclipse.microprofile.rest.client.RestClientBuilder; + import io.reactivex.Single; @Path("/test") @@ -18,6 +22,16 @@ public String getTest() { return "TEST"; } + @GET + @Path("/client") + public String client() throws Exception { + + RestInterface iface = RestClientBuilder.newBuilder() + .baseUrl(new URL("http", "localhost", 8080, "/rest")) + .build(RestInterface.class); + return iface.get(); + } + @GET @Path("/int/{val}") public Integer getInt(@PathParam("val") Integer val) { diff --git a/examples/strict/src/test/java/org/jboss/shamrock/example/test/RestClientITCase.java b/examples/strict/src/test/java/org/jboss/shamrock/example/test/RestClientITCase.java new file mode 100644 index 0000000000000..899cb493ebeed --- /dev/null +++ b/examples/strict/src/test/java/org/jboss/shamrock/example/test/RestClientITCase.java @@ -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 { + +} diff --git a/examples/strict/src/test/java/org/jboss/shamrock/example/test/RestClientTestCase.java b/examples/strict/src/test/java/org/jboss/shamrock/example/test/RestClientTestCase.java new file mode 100644 index 0000000000000..0d6ceb3c0963c --- /dev/null +++ b/examples/strict/src/test/java/org/jboss/shamrock/example/test/RestClientTestCase.java @@ -0,0 +1,29 @@ +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/test/client"); + 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())); + } +} diff --git a/jaxrs/deployment/pom.xml b/jaxrs/deployment/pom.xml index 630e153ed9892..dbe89bc9eedee 100644 --- a/jaxrs/deployment/pom.xml +++ b/jaxrs/deployment/pom.xml @@ -25,6 +25,10 @@ org.jboss.shamrock shamrock-jaxrs-runtime + + org.jboss.graalvm + graal-annotations + diff --git a/maven/src/main/java/org/jboss/shamrock/maven/NativeImageMojo.java b/maven/src/main/java/org/jboss/shamrock/maven/NativeImageMojo.java index a76fbad47a655..0d285ce599145 100644 --- a/maven/src/main/java/org/jboss/shamrock/maven/NativeImageMojo.java +++ b/maven/src/main/java/org/jboss/shamrock/maven/NativeImageMojo.java @@ -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 void execute() throws MojoExecutionException, MojoFailureException { } 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 void execute() throws MojoExecutionException, MojoFailureException { 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); diff --git a/pom.xml b/pom.xml index 1f6320d94681b..c9fb235deeb99 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,7 @@ 7.6.0.Final 2.2 2.1.12 + 1.0 @@ -76,6 +77,7 @@ bean-validation transactions agroal + rest-client @@ -211,6 +213,16 @@ shamrock-junit ${project.version} + + org.jboss.shamrock + shamrock-rest-client-deployment + ${project.version} + + + org.jboss.shamrock + shamrock-rest-client-runtime + ${project.version} + org.jboss.shamrock shamrock-shared-library-example @@ -422,6 +434,11 @@ microprofile-config-api 1.3 + + org.eclipse.microprofile.rest.client + microprofile-rest-client-api + ${microprofile-rest-client-api.version} + org.fakereplace fakereplace @@ -490,6 +507,11 @@ resteasy-cdi ${resteasy.version} + + org.jboss.resteasy + resteasy-client + ${resteasy.version} + org.jboss.resteasy resteasy-jaxrs diff --git a/rest-client/deployment/pom.xml b/rest-client/deployment/pom.xml new file mode 100644 index 0000000000000..84d44e9eae1bc --- /dev/null +++ b/rest-client/deployment/pom.xml @@ -0,0 +1,27 @@ + + + + shamrock-rest-client + org.jboss.shamrock + 1.0.0.Alpha1-SNAPSHOT + ../ + + 4.0.0 + + shamrock-rest-client-deployment + + + + org.jboss.shamrock + shamrock-core-deployment + + + org.jboss.shamrock + shamrock-rest-client-runtime + + + + + \ No newline at end of file diff --git a/rest-client/deployment/src/main/java/org/jboss/shamrock/restclient/RestClientProcessor.java b/rest-client/deployment/src/main/java/org/jboss/shamrock/restclient/RestClientProcessor.java new file mode 100644 index 0000000000000..550433a43c4cf --- /dev/null +++ b/rest-client/deployment/src/main/java/org/jboss/shamrock/restclient/RestClientProcessor.java @@ -0,0 +1,88 @@ +package org.jboss.shamrock.restclient; + +import java.lang.reflect.Modifier; + +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.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +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.RestClientProxy; + +class RestClientProcessor implements ResourceProcessor { + + 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()); + 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()); + 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; + } + processorContext.addProxyDefinition( theInfo.name().toString(), ResteasyClientProxy.class.getName()); + processorContext.addProxyDefinition( theInfo.name().toString(), RestClientProxy.class.getName()); + processorContext.addReflectiveClass(true, false, theInfo.name().toString()); + + } + } + } + + @Override + public int getPriority() { + return 1; + } +} diff --git a/rest-client/deployment/src/main/java/org/jboss/shamrock/restclient/RestClientSetup.java b/rest-client/deployment/src/main/java/org/jboss/shamrock/restclient/RestClientSetup.java new file mode 100644 index 0000000000000..b6d1de4956782 --- /dev/null +++ b/rest-client/deployment/src/main/java/org/jboss/shamrock/restclient/RestClientSetup.java @@ -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()); + } +} diff --git a/rest-client/deployment/src/main/resources/META-INF/services/org.jboss.shamrock.deployment.ShamrockSetup b/rest-client/deployment/src/main/resources/META-INF/services/org.jboss.shamrock.deployment.ShamrockSetup new file mode 100644 index 0000000000000..819367933b7c5 --- /dev/null +++ b/rest-client/deployment/src/main/resources/META-INF/services/org.jboss.shamrock.deployment.ShamrockSetup @@ -0,0 +1 @@ +org.jboss.shamrock.restclient.RestClientSetup diff --git a/rest-client/pom.xml b/rest-client/pom.xml new file mode 100644 index 0000000000000..5fbe0177b86d0 --- /dev/null +++ b/rest-client/pom.xml @@ -0,0 +1,18 @@ + + + + shamrock-parent + org.jboss.shamrock + 1.0.0.Alpha1-SNAPSHOT + + 4.0.0 + + shamrock-rest-client + pom + + deployment + runtime + + \ No newline at end of file diff --git a/rest-client/runtime/pom.xml b/rest-client/runtime/pom.xml new file mode 100644 index 0000000000000..58f7568e21806 --- /dev/null +++ b/rest-client/runtime/pom.xml @@ -0,0 +1,45 @@ + + + + shamrock-rest-client + org.jboss.shamrock + 1.0.0.Alpha1-SNAPSHOT + ../ + + 4.0.0 + + shamrock-rest-client-runtime + + + + org.jboss.shamrock + shamrock-core-runtime + + + org.eclipse.microprofile.rest.client + microprofile-rest-client-api + + + org.jboss.resteasy + resteasy-jaxrs + + + org.jboss.resteasy + resteasy-client + + + org.jboss.graalvm + graal-annotations + + + + + + + maven-dependency-plugin + + + + \ No newline at end of file diff --git a/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/BuilderResolver.java b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/BuilderResolver.java new file mode 100644 index 0000000000000..e9b09d59e58d9 --- /dev/null +++ b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/BuilderResolver.java @@ -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(); + } +} diff --git a/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/ConfigurationWrapper.java b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/ConfigurationWrapper.java new file mode 100644 index 0000000000000..05c44163a8627 --- /dev/null +++ b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/ConfigurationWrapper.java @@ -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 getProperties() { + return delegate.getProperties(); + } + + @Override + public Object getProperty(String name) { + return delegate.getProperty(name); + } + + @Override + public Collection getPropertyNames() { + return delegate.getPropertyNames(); + } + + @Override + public boolean isEnabled(Feature feature) { + return delegate.isEnabled(feature); + } + + @Override + public boolean isEnabled(Class 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, Integer> getContracts(Class componentClass) { + Map, Integer> contracts = new HashMap<>(); + contracts.putAll(getLocalContracts(componentClass)); + contracts.putAll(delegate.getContracts(componentClass)); + return contracts; + } + + private Map, ? extends Integer> getLocalContracts(Class componentClass) { + if (localClassContracts.containsKey(componentClass)) { + return localClassContracts.get(componentClass); + } else { + return Collections.emptyMap(); + } + } + + @Override + public Set> getClasses() { + return delegate.getClasses(); + } + + @Override + public Set getInstances() { + return delegate.getInstances(); + } + + void registerLocalContract(Class provider, Map, Integer> contracts) { + localClassContracts.put(provider, contracts); + } + + protected Map, Map, Integer>> localClassContracts = new HashMap<>(); + + private final Configuration delegate; +} diff --git a/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/DefaultResponseExceptionMapper.java b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/DefaultResponseExceptionMapper.java new file mode 100644 index 0000000000000..25f6922e96896 --- /dev/null +++ b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/DefaultResponseExceptionMapper.java @@ -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; + } +} diff --git a/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/ExceptionMapping.java b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/ExceptionMapping.java new file mode 100644 index 0000000000000..3f1ff6e76fd12 --- /dev/null +++ b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/ExceptionMapping.java @@ -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 instances) { + this.instances = instances; + } + + @Override + public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { + + Response response = new PartialResponse(responseContext); + + Map 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, Integer> errors = new HashMap<>(); + + mappers.forEach((m, i) -> { + Optional t = Optional.ofNullable(m.toThrowable(response)); + errors.put(t, i); + }); + + Optional prioritised = Optional.empty(); + for (Optional 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 instances; +} diff --git a/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/PartialResponse.java b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/PartialResponse.java new file mode 100644 index 0000000000000..2b32472df4dc8 --- /dev/null +++ b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/PartialResponse.java @@ -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 readEntity(Class 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 readEntity(GenericType entityType) { + throw notSupported(); + } + + @Override + public T readEntity(Class entityType, Annotation[] annotations) { + throw notSupported(); + } + + @Override + public T readEntity(GenericType 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 getAllowedMethods() { + return responseContext.getAllowedMethods(); + } + + @Override + public Map 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 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 getMetadata() { + MultivaluedMap metaData = new MultivaluedMapImpl(); + // TODO + return metaData; + } + + @Override + public MultivaluedMap getStringHeaders() { + return responseContext.getHeaders(); + } + + @Override + public String getHeaderString(String name) { + return responseContext.getHeaderString(name); + } + + private final transient ClientResponseContext responseContext; +} diff --git a/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/RestClientBuilderImpl.java b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/RestClientBuilderImpl.java new file mode 100644 index 0000000000000..ccecdb378fa7f --- /dev/null +++ b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/RestClientBuilderImpl.java @@ -0,0 +1,507 @@ +/** + * 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.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 build(Class 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 noProxyHosts = Arrays.asList( + System.getProperty("http.nonProxyHosts", "localhost|127.*|[::1]").split("|")); + String proxyHost = System.getProperty("http.proxyHost"); + + T actualClient; + ResteasyClient client; + 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 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 void verifyInterface(Class 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 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 allVariables = new HashSet<>(template.getPathParamNamesInDeclarationOrder()); + Map 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, 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, 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, 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, Integer> map) { + + + if (o instanceof ResponseExceptionMapper) { + + //local + ResponseExceptionMapper mapper = (ResponseExceptionMapper) o; + HashMap, 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 getLocalProviderInstances() { + return localProviderInstances; + } + + public void registerLocalProviderInstance(Object provider, Map, 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 localProviderInstances = new HashSet(); +} diff --git a/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/RestClientDelegateBean.java b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/RestClientDelegateBean.java new file mode 100644 index 0000000000000..5a1a9f613a602 --- /dev/null +++ b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/RestClientDelegateBean.java @@ -0,0 +1,167 @@ +/** + * 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.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.enterprise.context.Dependent; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Default; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.inject.spi.PassivationCapable; +import javax.enterprise.util.AnnotationLiteral; + +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.inject.RestClient; + +public class RestClientDelegateBean implements Bean, PassivationCapable { + + public static final String REST_URL_FORMAT = "%s/mp-rest/url"; + + public static final String REST_SCOPE_FORMAT = "%s/mp-rest/scope"; + + private final Class proxyType; + + private final Class scope; + + private final BeanManager beanManager; + + private final Config config; + + RestClientDelegateBean(Class proxyType, BeanManager beanManager) { + this.proxyType = proxyType; + this.beanManager = beanManager; + this.config = ConfigProvider.getConfig(); + this.scope = this.resolveScope(); + } + @Override + public String getId() { + return proxyType.getName(); + } + + @Override + public Class getBeanClass() { + return proxyType; + } + + @Override + public Set getInjectionPoints() { + return Collections.emptySet(); + } + + @Override + public boolean isNullable() { + return false; + } + + @Override + public Object create(CreationalContext creationalContext) { + 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); + } + } + + @Override + public void destroy(Object instance, CreationalContext creationalContext) { + + } + + @Override + public Set getTypes() { + return Collections.singleton(proxyType); + } + + @Override + public Set getQualifiers() { + Set qualifiers = new HashSet(); + qualifiers.add(new AnnotationLiteral() { }); + qualifiers.add(new AnnotationLiteral() { }); + qualifiers.add(RestClient.LITERAL); + return qualifiers; + } + + @Override + public Class getScope() { + return scope; + } + + @Override + public String getName() { + return proxyType.getName(); + } + + @Override + public Set> getStereotypes() { + return Collections.emptySet(); + } + + @Override + public boolean isAlternative() { + return false; + } + + private String getBaseUrl() { + String property = String.format(REST_URL_FORMAT, proxyType.getName()); + return config.getValue(property, String.class); + } + + private Class resolveScope() { + + String property = String.format(REST_SCOPE_FORMAT, proxyType.getName()); + String configuredScope = config.getOptionalValue(property, String.class).orElse(null); + + if (configuredScope != null) { + try { + return (Class) Class.forName(configuredScope); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid scope: " + configuredScope, e); + } + } + + List possibleScopes = new ArrayList<>(); + Annotation[] annotations = proxyType.getDeclaredAnnotations(); + for (Annotation annotation : annotations) { + if (beanManager.isScope(annotation.annotationType())) { + possibleScopes.add(annotation); + } + } + if (possibleScopes.isEmpty()) { + return Dependent.class; + } else if (possibleScopes.size() == 1) { + return possibleScopes.get(0).annotationType(); + } else { + throw new IllegalArgumentException("Ambiguous scope definition on " + proxyType + ": " + possibleScopes); + } + } + +} \ No newline at end of file diff --git a/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/RestClientProxy.java b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/RestClientProxy.java new file mode 100644 index 0000000000000..ed49e33013fc8 --- /dev/null +++ b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/RestClientProxy.java @@ -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(); + +} diff --git a/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/graal/ClientBuilderReplacement.java b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/graal/ClientBuilderReplacement.java new file mode 100644 index 0000000000000..cc6bfc87b463f --- /dev/null +++ b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/graal/ClientBuilderReplacement.java @@ -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(); + } +} diff --git a/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/graal/RestClientBuilderResolverReplacement.java b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/graal/RestClientBuilderResolverReplacement.java new file mode 100644 index 0000000000000..6285a7a842543 --- /dev/null +++ b/rest-client/runtime/src/main/java/org/jboss/shamrock/restclient/runtime/graal/RestClientBuilderResolverReplacement.java @@ -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(); + } +} diff --git a/rest-client/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver b/rest-client/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver new file mode 100644 index 0000000000000..a4a3dbfb02ada --- /dev/null +++ b/rest-client/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver @@ -0,0 +1 @@ +org.jboss.shamrock.restclient.runtime.BuilderResolver \ No newline at end of file