From 3447679f81588c7c6ac9fe472a13c5f34daeaf74 Mon Sep 17 00:00:00 2001 From: Guillaume Dufour Date: Sat, 14 Sep 2019 03:58:39 +0200 Subject: [PATCH] Init CXF extension Add cxf server side extension Docs Unit tests fix #4005 --- bom/deployment/pom.xml | 5 + bom/runtime/pom.xml | 70 +++++ .../builditem/FeatureBuildItem.java | 1 + docs/src/main/asciidoc/index.adoc | 1 + .../main/asciidoc/soap-web-service-guide.adoc | 244 ++++++++++++++++++ extensions/cxf/deployment/pom.xml | 72 ++++++ .../io/quarkus/cxf/deployment/CxfConfig.java | 23 ++ .../quarkus/cxf/deployment/CxfProcessor.java | 203 +++++++++++++++ .../deployment/CxfServerConfigBuildItem.java | 28 ++ .../cxf/deployment/WebServiceBuildItem.java | 15 ++ .../cxf/deployment/test/CxfServiceTest.java | 98 +++++++ .../io/quarkus/cxf/deployment/test/Fruit.java | 11 + .../cxf/deployment/test/FruitAdapter.java | 16 ++ .../cxf/deployment/test/FruitImpl.java | 53 ++++ .../cxf/deployment/test/FruitWebService.java | 18 ++ .../deployment/test/FruitWebServiceImpl.java | 34 +++ .../src/test/resources/application.properties | 2 + extensions/cxf/pom.xml | 21 ++ extensions/cxf/runtime/pom.xml | 83 ++++++ .../AbstractCxfWebServiceProducer.java | 5 + .../cxf/runtime/CXFQuarkusServlet.java | 78 ++++++ .../resources/META-INF/quarkus-extension.yaml | 15 ++ extensions/pom.xml | 3 + 23 files changed, 1099 insertions(+) create mode 100644 docs/src/main/asciidoc/soap-web-service-guide.adoc create mode 100644 extensions/cxf/deployment/pom.xml create mode 100644 extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfConfig.java create mode 100755 extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfProcessor.java create mode 100644 extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfServerConfigBuildItem.java create mode 100644 extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/WebServiceBuildItem.java create mode 100644 extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/CxfServiceTest.java create mode 100644 extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/Fruit.java create mode 100644 extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitAdapter.java create mode 100644 extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitImpl.java create mode 100644 extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebService.java create mode 100644 extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebServiceImpl.java create mode 100644 extensions/cxf/deployment/src/test/resources/application.properties create mode 100644 extensions/cxf/pom.xml create mode 100644 extensions/cxf/runtime/pom.xml create mode 100644 extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/AbstractCxfWebServiceProducer.java create mode 100644 extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/CXFQuarkusServlet.java create mode 100644 extensions/cxf/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index c7526647b8595..59f1879ee916a 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -266,6 +266,11 @@ quarkus-resteasy-jaxb-deployment ${project.version} + + io.quarkus + quarkus-cxf-deployment + ${project.version} + io.quarkus quarkus-smallrye-jwt-deployment diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 5b86261c611c3..0b310dc48e943 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -173,6 +173,11 @@ 1.0.1.Final 8.0.1 1.13.0 + 1.2 + 3.3.4 + 2.0.0.Final + 1.4.0 + 1.1.1 @@ -542,6 +547,11 @@ quarkus-resteasy-server-common ${project.version} + + io.quarkus + quarkus-cxf + ${project.version} + io.quarkus quarkus-narayana-jta @@ -1303,6 +1313,11 @@ jboss-jaxb-api_2.3_spec ${jboss-jaxb-api_2.3_spec.version} + + jakarta.jws + jakarta.jws-api + ${jakarta-jws-api.version} + org.jboss.spec.javax.ws.rs jboss-jaxrs-api_2.1_spec @@ -1328,6 +1343,50 @@ artemis-commons ${artemis.version} + + javax.xml.soap + javax.xml.soap-api + ${javax-xml-soap.version} + provided + + + org.apache.cxf + cxf-rt-frontend-jaxws + ${cxf.version} + + + org.jboss.spec.javax.annotation + jboss-annotations-api_1.3_spec + + + javax.annotation + javax.annotation-api + + + javax.activation + activation + + + javax.xml.soap + javax.xml.soap-api + + + + + org.apache.cxf + cxf-rt-transports-http + ${cxf.version} + + + org.jboss.spec.javax.annotation + jboss-annotations-api_1.3_spec + + + javax.annotation + javax.annotation-api + + + org.apache.httpcomponents httpclient @@ -1648,6 +1707,17 @@ stm ${narayana.version} + + org.jboss.spec.javax.xml.ws + jboss-jaxws-api_2.3_spec + ${jaxws.version} + + + org.jboss.spec.javax.annotation + jboss-annotations-api_1.3_spec + + + org.jboss.resteasy resteasy-core diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java index afc54a00a8a52..310d57c7fa753 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java @@ -14,6 +14,7 @@ public final class FeatureBuildItem extends MultiBuildItem { public static final String ARTEMIS_CORE = "artemis-core"; public static final String ARTEMIS_JMS = "artemis-jms"; public static final String CDI = "cdi"; + public static final String CXF = "cxf"; public static final String DYNAMODB = "dynamodb"; public static final String ELASTICSEARCH_REST_CLIENT = "elasticsearch-rest-client"; public static final String FLYWAY = "flyway"; diff --git a/docs/src/main/asciidoc/index.adoc b/docs/src/main/asciidoc/index.adoc index 56b6c557bff6c..05536f14ec773 100644 --- a/docs/src/main/asciidoc/index.adoc +++ b/docs/src/main/asciidoc/index.adoc @@ -58,6 +58,7 @@ include::quarkus-intro.adoc[tag=intro] * link:jms.html[Using Artemis JMS Client] * link:reactive-routes.adoc[Using Reactive Routes] * link:camel.adoc[Apache Camel] +* link:soap-web-service-guide.html[Writing SOAP Web Services] * link:faq.html[FAQs] diff --git a/docs/src/main/asciidoc/soap-web-service-guide.adoc b/docs/src/main/asciidoc/soap-web-service-guide.adoc new file mode 100644 index 0000000000000..94f4987dff772 --- /dev/null +++ b/docs/src/main/asciidoc/soap-web-service-guide.adoc @@ -0,0 +1,244 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - Writing Soap Web Services + +include::./attributes.adoc[] + +SOAP (Simple Object Access Protocol) is an exchange protocole used by old web service before the developement of REST technology. + +In this guide, we see how you can get your web services to consume and produce SOAP payloads. + +== Prerequisites + +To complete this guide, you need: + +* less than 15 minutes +* an IDE +* JDK 1.8+ installed with `JAVA_HOME` configured appropriately +* Apache Maven 3.5.3+ + +== Architecture + +The application built in this guide is quite simple: the user can add elements in a list using a form and the list is updated. + +All the information between the browser and the server are formatted as SOAP. + +== Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can go right to the completed example. + +Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive]. + +The solution is located in the `cxf` {quickstarts-tree-url}/cxf[directory]. + +== Creating the Maven project + +First, we need a new project. Create a new project with the following command: + +[source,shell,subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=jaxws \ + -Dextensions="cxf" +---- + +This command generates a Maven structure importing the CXF/JAX-WS extensions. + +== Creating your first SOAP Web service + +In this example, we will create an application to manage a list of fruits. + +First, let's create the `Fruit` bean as follows: + +[source,java] +---- +package org.acme.cxf; + +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +@XmlJavaTypeAdapter(FruitAdapter.class) +public interface Fruit { + + String getName(); + + String getDescription(); +} +---- + +And it's implementation `FruitImpl`: + +[source,java] +---- +package org.acme.cxf.impl; + +import java.util.Objects; +import org.acme.cxf.Fruit; + +import javax.xml.bind.annotation.XmlType; + +@XmlType(name = "Fruit") +public class FruitImpl implements Fruit { + + private String name; + + private String description; + + public FruitImpl() { + } + + public FruitImpl(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Fruit)) { + return false; + } + + Fruit other = (Fruit) obj; + + return Objects.equals(other.getName(), this.getName()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getName()); + } +} +---- + +To finish about the data the `FruitAdapter` + +[source,java] +---- +package org.acme.cxf; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +import org.acme.cxf.Fruit; +import org.acme.cxf.impl.FruitImpl; + +public class FruitAdapter extends XmlAdapter { + public FruitImpl marshal(Fruit Fruit) throws Exception { + if (Fruit instanceof FruitImpl) { + return (FruitImpl) Fruit; + } + return new FruitImpl(Fruit.getName(), Fruit.getDescription()); + } + + public Fruit unmarshal(FruitImpl Fruit) throws Exception { + return Fruit; + } +} +---- + + + +Now, create the `org.acme.cxf.soap.FruitWebService` class as follows: + +[source,java] +---- +package org.acme.cxf; + +import java.util.Set; + +import javax.jws.WebMethod; +import javax.jws.WebParam; +import javax.jws.WebService; + +@WebService +public interface FruitWebService { + + @WebMethod + Set list(); + + @WebMethod + Set add(Fruit fruit); + + @WebMethod + Set delete(Fruit fruit); +} +---- + +Then, create the `org.acme.cxf.soap.impl.FruitWebServiceImpl` class as follows: + +[source,java] +---- +package org.acme.cxf.impl; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Set; + +import javax.jws.WebService; + +import org.acme.cxf.Fruit; +import org.acme.cxf.FruitWebService; + +@WebService(endpointInterface = "org.acme.cxf.FruitWebService") +public class FruitWebServiceImpl implements FruitWebService { + + private Set fruits = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>())); + + public FruitWebServiceImpl() { + fruits.add(new FruitImpl("Apple", "Winter fruit")); + fruits.add(new FruitImpl("Pineapple", "Tropical fruit")); + } + + @Override + public Set list() { + return fruits; + } + + @Override + public Set add(Fruit fruit) { + fruits.add(fruit); + return fruits; + } + + @Override + public Set delete(Fruit fruit) { + fruits.remove(fruit); + return fruits; + } +} +---- + +The implementation is pretty straightforward and you just need to define your endpoints using the application.properties`. + +[source,properties] +---- +quarkus.cxf.path=/cxf +quarkus.cxf.webservice."/fruit"=org.acme.cxf.impl.FruitWebServiceImpl +---- + + +== Conclusion + +Creating SOAP Web services with Quarkus is easy as it relies on proven and well known technologies. + +As usual, Quarkus further simplifies things under the hood when running your application as a native executable. + diff --git a/extensions/cxf/deployment/pom.xml b/extensions/cxf/deployment/pom.xml new file mode 100644 index 0000000000000..8504fb04eb26c --- /dev/null +++ b/extensions/cxf/deployment/pom.xml @@ -0,0 +1,72 @@ + + + + quarkus-cxf-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-cxf-deployment + Quarkus - CXF - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-cxf + + + io.quarkus + quarkus-undertow-deployment + + + org.jboss.spec.javax.xml.ws + jboss-jaxws-api_2.3_spec + + + io.quarkus + quarkus-junit5-internal + test + + + commons-io + commons-io + test + + + io.rest-assured + rest-assured + test + + + jakarta.jws + jakarta.jws-api + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfConfig.java b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfConfig.java new file mode 100644 index 0000000000000..6efba752f1f6a --- /dev/null +++ b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfConfig.java @@ -0,0 +1,23 @@ +package io.quarkus.cxf.deployment; + +import java.util.Map; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot +final class CxfConfig { + + /** + * Set this to override the default path for JAX-RS resources if there are no + * annotated application classes. + */ + @ConfigItem(defaultValue = "/") + String path; + + /** + * Choose the path of each web services. + */ + @ConfigItem(name = "webservice") + Map webServicesPaths; +} \ No newline at end of file diff --git a/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfProcessor.java b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfProcessor.java new file mode 100755 index 0000000000000..eab2fb42f9364 --- /dev/null +++ b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfProcessor.java @@ -0,0 +1,203 @@ +package io.quarkus.cxf.deployment; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.Type; + +import io.quarkus.arc.Unremovable; +import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; +import io.quarkus.arc.deployment.GeneratedBeanBuildItem; +import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.cxf.runtime.AbstractCxfWebServiceProducer; +import io.quarkus.cxf.runtime.CXFQuarkusServlet; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; +import io.quarkus.deployment.util.HashUtil; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.undertow.deployment.ServletBuildItem; +import io.quarkus.undertow.deployment.ServletInitParamBuildItem; + +/** + * Processor that finds JAX-RS classes in the deployment + */ +public class CxfProcessor { + + private static final String JAX_WS_CXF_SERVLET = "org.apache.cxf.transport.servlet.CXFNonSpringServlet"; + + private static final DotName WEBSERVICE_ANNOTATION = DotName.createSimple("javax.jws.WebService"); + private static final DotName ADAPTER_ANNOTATION = DotName + .createSimple("javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter"); + private static final DotName UNREMOVABLE_BEAN = DotName.createSimple(AbstractCxfWebServiceProducer.class.getName()); + + @BuildStep + UnremovableBeanBuildItem markBeansAsUnremovable() { + return new UnremovableBeanBuildItem(beanInfo -> { + Set types = beanInfo.getTypes(); + for (Type t : types) { + if (UNREMOVABLE_BEAN.equals(t.name())) { + return true; + } + } + return false; + }); + } + + /** + * JAX-RS configuration. + */ + CxfConfig cxfConfig; + + // @BuildStep + // void build(BuildProducer reflectiveClass, + // BuildProducer resourceBundle, + // BuildProducer serviceProvider) { + // reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, + // ProviderImpl.class.getName())); + // + // serviceProvider.produce(new ServiceProviderBuildItem(Provider.class.getName(), + // ProviderImpl.class.getName())); + // } + + @BuildStep + public void build( + BuildProducer reflectiveClass, + BuildProducer reflectiveHierarchy, + BuildProducer proxyDefinition, + BuildProducer resource, + BuildProducer runtimeClasses, + BuildProducer transformers, + BuildProducer cxfServerConfig, + BuildProducer unremovableBeans, + CombinedIndexBuildItem combinedIndexBuildItem, + BeanArchiveIndexBuildItem beanArchiveIndexBuildItem) throws Exception { + IndexView index = combinedIndexBuildItem.getIndex(); + + for (AnnotationInstance annotation : index.getAnnotations(WEBSERVICE_ANNOTATION)) { + if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) { + reflectiveClass + .produce(new ReflectiveClassBuildItem(true, true, annotation.target().asClass().name().toString())); + } + } + + for (AnnotationInstance annotation : index.getAnnotations(ADAPTER_ANNOTATION)) { + if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) { + reflectiveClass + .produce(new ReflectiveClassBuildItem(true, true, annotation.target().asClass().name().toString())); + } + AnnotationValue value = annotation.value(); + reflectiveClass + .produce(new ReflectiveClassBuildItem(true, true, value.asClass().name().toString())); + } + + Map cxfInitParameters = new HashMap<>(); + + cxfServerConfig.produce(new CxfServerConfigBuildItem(cxfConfig.path, cxfInitParameters)); + } + + @BuildStep + public void configToBuildItem(BuildProducer webServiceBuildItems) { + for (Entry webServicesByPath : cxfConfig.webServicesPaths.entrySet()) { + webServiceBuildItems.produce(new WebServiceBuildItem(webServicesByPath.getValue())); + } + } + + @BuildStep + public void build( + Optional cxfServerConfig, + BuildProducer feature, + BuildProducer servlet, + BuildProducer reflectiveClass, + BuildProducer servletInitParameters) throws Exception { + feature.produce(new FeatureBuildItem(FeatureBuildItem.CXF)); + + if (cxfServerConfig.isPresent()) { + String path = cxfServerConfig.get().getPath(); + + String mappingPath = getMappingPath(path); + servlet.produce(ServletBuildItem.builder(JAX_WS_CXF_SERVLET, CXFQuarkusServlet.class.getName()) + .setLoadOnStartup(1).addMapping(mappingPath).setAsyncSupported(true).build()); + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, CXFQuarkusServlet.class.getName())); + + for (Entry initParameter : cxfServerConfig.get().getInitParameters().entrySet()) { + servletInitParameters.produce(new ServletInitParamBuildItem(initParameter.getKey(), initParameter.getValue())); + } + + for (Entry webServicesByPath : cxfConfig.webServicesPaths.entrySet()) { + CXFQuarkusServlet.publish(webServicesByPath.getKey(), webServicesByPath.getValue()); + } + } + } + + private String getMappingPath(String path) { + String mappingPath; + if (path.endsWith("/")) { + mappingPath = path + "*"; + } else { + mappingPath = path + "/*"; + } + return mappingPath; + } + + @BuildStep + public void createBeans( + BuildProducer unremovableBeans, + BuildProducer generatedBeans, + List webServices) throws Exception { + for (WebServiceBuildItem webService : webServices) { + ClassOutput classOutput = new GeneratedBeanGizmoAdaptor(generatedBeans); + String webServiceName = webService.getWebServiceClass(); + ClassCreator classCreator = ClassCreator.builder().classOutput(classOutput) + .className(webServiceName + "Producer") + .superClass(AbstractCxfWebServiceProducer.class) + .build(); + classCreator.addAnnotation(ApplicationScoped.class); + + unremovableBeans.produce(new UnremovableBeanBuildItem( + new UnremovableBeanBuildItem.BeanClassNameExclusion(webServiceName))); + + MethodCreator namedWebServiceMethodCreator = classCreator.getMethodCreator( + "createWebService_" + HashUtil.sha1(webServiceName), + webServiceName); + namedWebServiceMethodCreator.addAnnotation(ApplicationScoped.class); + namedWebServiceMethodCreator.addAnnotation(Unremovable.class); + namedWebServiceMethodCreator.addAnnotation(Produces.class); + namedWebServiceMethodCreator.addAnnotation(AnnotationInstance.create(DotNames.NAMED, null, + new AnnotationValue[] { AnnotationValue.createStringValue("value", webServiceName) })); + + ResultHandle namedWebService = namedWebServiceMethodCreator + .newInstance(MethodDescriptor.ofConstructor(webServiceName)); + + namedWebServiceMethodCreator.returnValue(namedWebService); + classCreator.close(); + } + + } + +} diff --git a/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfServerConfigBuildItem.java b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfServerConfigBuildItem.java new file mode 100644 index 0000000000000..f989b8724c6c0 --- /dev/null +++ b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/CxfServerConfigBuildItem.java @@ -0,0 +1,28 @@ +package io.quarkus.cxf.deployment; + +import java.util.Map; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * A build item that represents the configuration of the RESTEasy server. + */ +public final class CxfServerConfigBuildItem extends SimpleBuildItem { + + private final String path; + + private final Map initParameters; + + public CxfServerConfigBuildItem(String path, Map initParameters) { + this.path = path; + this.initParameters = initParameters; + } + + public String getPath() { + return path; + } + + public Map getInitParameters() { + return initParameters; + } +} diff --git a/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/WebServiceBuildItem.java b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/WebServiceBuildItem.java new file mode 100644 index 0000000000000..3d0fb318b83d0 --- /dev/null +++ b/extensions/cxf/deployment/src/main/java/io/quarkus/cxf/deployment/WebServiceBuildItem.java @@ -0,0 +1,15 @@ +package io.quarkus.cxf.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class WebServiceBuildItem extends MultiBuildItem { + private final String webServiceClass; + + public WebServiceBuildItem(String webServiceClass) { + this.webServiceClass = webServiceClass; + } + + public String getWebServiceClass() { + return webServiceClass; + } +} diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/CxfServiceTest.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/CxfServiceTest.java new file mode 100644 index 0000000000000..79daa30f1ba2b --- /dev/null +++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/CxfServiceTest.java @@ -0,0 +1,98 @@ +package io.quarkus.cxf.deployment.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.util.function.Supplier; + +import javax.xml.namespace.QName; +import javax.xml.parsers.*; +import javax.xml.ws.Service; +import javax.xml.ws.soap.SOAPBinding; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; +import io.restassured.response.Response; + +public class CxfServiceTest { + + @RegisterExtension + public static final QuarkusDevModeTest test = new QuarkusDevModeTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClass(FruitWebServiceImpl.class) + .addClass(FruitWebService.class) + .addClass(Fruit.class) + .addClass(FruitImpl.class) + .addClass(FruitAdapter.class) + .addAsResource("application.properties"); + } + }); + + private static QName SERVICE_NAME = new QName("http://test.deployment.cxf.quarkus.io/", "FruitWebServiceImpl"); + private static QName PORT_NAME = new QName("http://test.deployment.cxf.quarkus.io/", "FruitWebServiceImplPortType"); + + private Service service; + private FruitWebService fruitProxy; + private FruitWebServiceImpl fruitImpl; + + { + service = Service.create(SERVICE_NAME); + final String endpointAddress = "http://localhost:8080/fruit"; + service.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress); + } + + @BeforeEach + public void reinstantiateBaeldungInstances() { + fruitImpl = new FruitWebServiceImpl(); + fruitProxy = service.getPort(PORT_NAME, FruitWebService.class); + } + + @Test + public void whenUsingHelloMethod_thenCorrect() { + String xml = "\n" + + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + String cnt = ""; + try { + Response response = RestAssured.given().header("Content-Type", "text/xml").and().body(xml).when().post("/fruit"); + response.then().statusCode(200); + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(response.body().asInputStream()); + doc.getDocumentElement().normalize(); + XPath xpath = XPathFactory.newInstance().newXPath(); + + cnt = xpath.compile("/Envelope/Body/countResponse/return").evaluate(doc); + } catch (XPathExpressionException e) { + e.printStackTrace(); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } + //Assertions.assertEquals(xmlrsp, response.body().asString()); + Assertions.assertEquals("2", cnt); + } +} diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/Fruit.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/Fruit.java new file mode 100644 index 0000000000000..b5499774c362e --- /dev/null +++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/Fruit.java @@ -0,0 +1,11 @@ +package io.quarkus.cxf.deployment.test; + +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +@XmlJavaTypeAdapter(FruitAdapter.class) +public interface Fruit { + + String getName(); + + String getDescription(); +} diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitAdapter.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitAdapter.java new file mode 100644 index 0000000000000..51e7cfd2fff79 --- /dev/null +++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitAdapter.java @@ -0,0 +1,16 @@ +package io.quarkus.cxf.deployment.test; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +public class FruitAdapter extends XmlAdapter { + public FruitImpl marshal(Fruit fruit) throws Exception { + if (fruit instanceof FruitImpl) { + return (FruitImpl) fruit; + } + return new FruitImpl(fruit.getName(), fruit.getDescription()); + } + + public Fruit unmarshal(FruitImpl fruit) throws Exception { + return fruit; + } +} \ No newline at end of file diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitImpl.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitImpl.java new file mode 100644 index 0000000000000..220ca5bc17024 --- /dev/null +++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitImpl.java @@ -0,0 +1,53 @@ +package io.quarkus.cxf.deployment.test; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlType; + +@XmlType(name = "Fruit") +public class FruitImpl implements Fruit { + + private String name; + + private String description; + + public FruitImpl() { + } + + public FruitImpl(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Fruit)) { + return false; + } + + Fruit other = (Fruit) obj; + + return Objects.equals(other.getName(), this.getName()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getName()); + } +} diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebService.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebService.java new file mode 100644 index 0000000000000..76326e02daa33 --- /dev/null +++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebService.java @@ -0,0 +1,18 @@ +package io.quarkus.cxf.deployment.test; + +import javax.jws.WebMethod; +import javax.jws.WebParam; +import javax.jws.WebService; + +@WebService(targetNamespace = "http://test.deployment.cxf.quarkus.io/", name = "FruitWebServiceImplPortType") +public interface FruitWebService { + + @WebMethod + int count(); + + @WebMethod + void add(@WebParam(name = "fruit") Fruit fruit); + + @WebMethod + void delete(@WebParam(name = "fruit") Fruit fruit); +} diff --git a/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebServiceImpl.java b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebServiceImpl.java new file mode 100644 index 0000000000000..c2b596843d090 --- /dev/null +++ b/extensions/cxf/deployment/src/test/java/io/quarkus/cxf/deployment/test/FruitWebServiceImpl.java @@ -0,0 +1,34 @@ +package io.quarkus.cxf.deployment.test; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Set; + +import javax.jws.WebParam; +import javax.jws.WebService; + +@WebService(endpointInterface = "org.acme.cxf.FruitWebService", targetNamespace = "http://test.deployment.cxf.quarkus.io/", portName = "FruitWebServiceImplPortType", serviceName = "FruitWebServiceImpl") +public class FruitWebServiceImpl implements FruitWebService { + + private Set fruits = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>())); + + public FruitWebServiceImpl() { + fruits.add(new FruitImpl("Apple", "Winter fruit")); + fruits.add(new FruitImpl("Pineapple", "Tropical fruit")); + } + + @Override + public int count() { + return (fruits != null ? fruits.size() : 0); + } + + @Override + public void add(@WebParam(name = "fruit") Fruit fruit) { + fruits.add(fruit); + } + + @Override + public void delete(@WebParam(name = "fruit") Fruit fruit) { + fruits.remove(fruit); + } +} diff --git a/extensions/cxf/deployment/src/test/resources/application.properties b/extensions/cxf/deployment/src/test/resources/application.properties new file mode 100644 index 0000000000000..8774667780c5f --- /dev/null +++ b/extensions/cxf/deployment/src/test/resources/application.properties @@ -0,0 +1,2 @@ +quarkus.cxf.path=/ +quarkus.cxf.webservice."/fruit"=io.quarkus.cxf.deployment.test.FruitWebServiceImpl diff --git a/extensions/cxf/pom.xml b/extensions/cxf/pom.xml new file mode 100644 index 0000000000000..c9cb89668fa13 --- /dev/null +++ b/extensions/cxf/pom.xml @@ -0,0 +1,21 @@ + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-cxf-parent + Quarkus - CXF + pom + + deployment + runtime + + + diff --git a/extensions/cxf/runtime/pom.xml b/extensions/cxf/runtime/pom.xml new file mode 100644 index 0000000000000..fbf03df0d0521 --- /dev/null +++ b/extensions/cxf/runtime/pom.xml @@ -0,0 +1,83 @@ + + + + quarkus-cxf-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-cxf + Quarkus - CXF - Runtime + CXF + + + io.quarkus + quarkus-arc + + + javax.annotation + javax.annotation-api + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + + + jakarta.xml.bind + jakarta.xml.bind-api + + + javax.annotation + javax.annotation-api + + + javax.activation + javax.activation-api + + + javax.xml.soap + javax.xml.soap-api + + + + + jakarta.servlet + jakarta.servlet-api + + + org.apache.cxf + cxf-rt-transports-http + + + org.slf4j + slf4j-api + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/AbstractCxfWebServiceProducer.java b/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/AbstractCxfWebServiceProducer.java new file mode 100644 index 0000000000000..bfed1009aa0cf --- /dev/null +++ b/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/AbstractCxfWebServiceProducer.java @@ -0,0 +1,5 @@ +package io.quarkus.cxf.runtime; + +public class AbstractCxfWebServiceProducer { + //TODO +} diff --git a/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/CXFQuarkusServlet.java b/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/CXFQuarkusServlet.java new file mode 100644 index 0000000000000..ea13329c7760d --- /dev/null +++ b/extensions/cxf/runtime/src/main/java/io/quarkus/cxf/runtime/CXFQuarkusServlet.java @@ -0,0 +1,78 @@ +package io.quarkus.cxf.runtime; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletConfig; + +import org.apache.cxf.Bus; +import org.apache.cxf.BusFactory; +import org.apache.cxf.frontend.ServerFactoryBean; +import org.apache.cxf.transport.servlet.CXFNonSpringServlet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.quarkus.arc.Arc; + +public class CXFQuarkusServlet extends CXFNonSpringServlet { + + private static final Logger LOGGER = LoggerFactory.getLogger(CXFQuarkusServlet.class); + + public static class WebServiceConfig { + private String path; + private String className; + + public WebServiceConfig(String path, String className) { + super(); + this.path = path; + this.className = className; + } + + public String getClassName() { + return className; + } + + public String getPath() { + return path; + } + + @Override + public String toString() { + return "Web Service " + className + " on " + path; + } + + } + + private static final long serialVersionUID = 1L; + + private static final List WEB_SERVICES = new ArrayList<>(); + + @Override + public void loadBus(ServletConfig servletConfig) { + super.loadBus(servletConfig); + + // You could add the endpoint publish codes here + Bus bus = getBus(); + BusFactory.setDefaultBus(bus); + + // You can als use the simple frontend API to do this + ServerFactoryBean factory = new ServerFactoryBean(); + factory.setBus(bus); + + for (WebServiceConfig config : WEB_SERVICES) { + Object instanceService = Arc.container().instance(config.getClassName()).get(); + if (instanceService != null) { + factory.setServiceBean(instanceService); + factory.setAddress(config.getPath()); + factory.create(); + LOGGER.info(config.toString() + " available."); + } else { + LOGGER.error("Cannot initialize " + config.toString()); + } + } + } + + public static void publish(String path, String webService) { + WEB_SERVICES.add(new WebServiceConfig(path, webService)); + } +} diff --git a/extensions/cxf/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/cxf/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..0efe882ba4c17 --- /dev/null +++ b/extensions/cxf/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,15 @@ +--- +name: "CXF" +metadata: + keywords: + - "cxf" + - "jaxws" + - "jax-ws" + - "web" + - "soap" + categories: + - "web" + - "reactive" + guide: "https://quarkus.io/guides/cxf-guide" + status: "stable" + unlisted: "true" diff --git a/extensions/pom.xml b/extensions/pom.xml index bf78ad80e2d89..cbbf8cfc9c9b3 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -54,6 +54,9 @@ smallrye-openapi swagger-ui + + cxf + vertx vertx-web