diff --git a/docs/antora.yml b/docs/antora.yml
index 85403940d63..a67eef1280e 100644
--- a/docs/antora.yml
+++ b/docs/antora.yml
@@ -33,6 +33,7 @@ asciidoc:
quarkus-version: 3.2.2.Final # replace ${quarkus.version}
graalvm-version: 22.3.2 # replace ${graalvm.version}
graalvm-docs-version: 22.3
+ mapstruct-version: 1.5.5.Final # replace ${mapstruct.version}
min-maven-version: 3.8.2 # replace ${min-maven-version}
target-maven-version: 3.9.3 # replace ${target-maven-version}
@@ -52,7 +53,7 @@ asciidoc:
link-quarkus-code-generator: code.quarkus.io
link-quarkus-cxf-doc: https://quarkiverse.github.io/quarkiverse-docs/quarkus-cxf/dev
link-quarkus-cxf-source: https://github.com/quarkiverse/quarkus-cxf/tree/main
-
+
# Misc
javaxOrJakartaPackagePrefix: jakarta # this can be switched to javax in older branches
diff --git a/docs/modules/ROOT/examples/components/mapstruct.yml b/docs/modules/ROOT/examples/components/mapstruct.yml
index 8efead02118..d35707a121e 100644
--- a/docs/modules/ROOT/examples/components/mapstruct.yml
+++ b/docs/modules/ROOT/examples/components/mapstruct.yml
@@ -2,11 +2,11 @@
# This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page
cqArtifactId: camel-quarkus-mapstruct
cqArtifactIdBase: mapstruct
-cqNativeSupported: false
-cqStatus: Preview
+cqNativeSupported: true
+cqStatus: Stable
cqDeprecated: false
cqJvmSince: 3.0.0
-cqNativeSince: n/a
+cqNativeSince: 3.0.0
cqCamelPartName: mapstruct
cqCamelPartTitle: MapStruct
cqCamelPartDescription: Type Conversion using Mapstruct
diff --git a/docs/modules/ROOT/pages/reference/extensions/mapstruct.adoc b/docs/modules/ROOT/pages/reference/extensions/mapstruct.adoc
index 1aa730b7c3e..1e234bacc75 100644
--- a/docs/modules/ROOT/pages/reference/extensions/mapstruct.adoc
+++ b/docs/modules/ROOT/pages/reference/extensions/mapstruct.adoc
@@ -4,17 +4,17 @@
= MapStruct
:linkattrs:
:cq-artifact-id: camel-quarkus-mapstruct
-:cq-native-supported: false
-:cq-status: Preview
-:cq-status-deprecation: Preview
+:cq-native-supported: true
+:cq-status: Stable
+:cq-status-deprecation: Stable
:cq-description: Type Conversion using Mapstruct
:cq-deprecated: false
:cq-jvm-since: 3.0.0
-:cq-native-since: n/a
+:cq-native-since: 3.0.0
ifeval::[{doc-show-badges} == true]
[.badges]
-[.badge-key]##JVM since##[.badge-supported]##3.0.0## [.badge-key]##Native##[.badge-unsupported]##unsupported##
+[.badge-key]##JVM since##[.badge-supported]##3.0.0## [.badge-key]##Native since##[.badge-supported]##3.0.0##
endif::[]
Type Conversion using Mapstruct
@@ -29,6 +29,10 @@ Please refer to the above link for usage and configuration details.
[id="extensions-mapstruct-maven-coordinates"]
== Maven coordinates
+https://{link-quarkus-code-generator}/?extension-search=camel-quarkus-mapstruct[Create a new project with this extension on {link-quarkus-code-generator}, window="_blank"]
+
+Or add the coordinates to your existing project:
+
[source,xml]
----
@@ -39,3 +43,57 @@ Please refer to the above link for usage and configuration details.
ifeval::[{doc-show-user-guide-link} == true]
Check the xref:user-guide/index.adoc[User guide] for more information about writing Camel Quarkus applications.
endif::[]
+
+[id="extensions-mapstruct-usage"]
+== Usage
+[id="extensions-mapstruct-usage-annotation-processor"]
+=== Annotation Processor
+
+To use MapStruct, you must configure your build to use an annotation processor.
+
+[id="extensions-mapstruct-usage-maven"]
+==== Maven
+
+[source,xml]
+----
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.mapstruct
+ mapstruct-processor
+ {mapstruct-version}
+
+
+
+
+
+----
+
+[id="extensions-mapstruct-usage-gradle"]
+==== Gradle
+
+[source,gradle]
+----
+dependencies {
+ annotationProcessor 'org.mapstruct:mapstruct-processor:{mapstruct-version}'
+ testAnnotationProcessor 'org.mapstruct:mapstruct-processor:{mapstruct-version}'
+}
+----
+
+[id="extensions-mapstruct-usage-mapper-definition-discovery"]
+=== Mapper definition discovery
+
+By default, {project-name} will automatically discover the package paths of your `@Mapper` annotated interfaces or abstract classes and
+pass them to the Camel MapStruct component.
+
+If you want finer control over the specific packages that are scanned, then you can set a configuration property in `application.properties`.
+
+[source,properties]
+----
+camel.component.mapstruct.mapper-package-name = com.first.package,org.second.package
+----
+
diff --git a/extensions-jvm/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/MapstructProcessor.java b/extensions-jvm/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/MapstructProcessor.java
deleted file mode 100644
index 3bfaa4cefdc..00000000000
--- a/extensions-jvm/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/MapstructProcessor.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.deployment;
-
-import io.quarkus.deployment.annotations.BuildStep;
-import io.quarkus.deployment.annotations.ExecutionTime;
-import io.quarkus.deployment.annotations.Record;
-import io.quarkus.deployment.builditem.FeatureBuildItem;
-import io.quarkus.deployment.pkg.steps.NativeBuild;
-import org.apache.camel.quarkus.core.JvmOnlyRecorder;
-import org.jboss.logging.Logger;
-
-class MapstructProcessor {
-
- private static final Logger LOG = Logger.getLogger(MapstructProcessor.class);
- private static final String FEATURE = "camel-mapstruct";
-
- @BuildStep
- FeatureBuildItem feature() {
- return new FeatureBuildItem(FEATURE);
- }
-
- /**
- * Remove this once this extension starts supporting the native mode.
- */
- @BuildStep(onlyIf = NativeBuild.class)
- @Record(value = ExecutionTime.RUNTIME_INIT)
- void warnJvmInNative(JvmOnlyRecorder recorder) {
- JvmOnlyRecorder.warnJvmInNative(LOG, FEATURE); // warn at build time
- recorder.warnJvmInNative(FEATURE); // warn at runtime
- }
-}
diff --git a/extensions-jvm/pom.xml b/extensions-jvm/pom.xml
index e16ab783eba..ec56a2f55f2 100644
--- a/extensions-jvm/pom.xml
+++ b/extensions-jvm/pom.xml
@@ -83,7 +83,6 @@
jt400
ldif
lucene
- mapstruct
mvel
printer
pulsar
diff --git a/extensions-jvm/mapstruct/deployment/pom.xml b/extensions/mapstruct/deployment/pom.xml
similarity index 100%
rename from extensions-jvm/mapstruct/deployment/pom.xml
rename to extensions/mapstruct/deployment/pom.xml
diff --git a/extensions/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/ConversionMethodInfoRuntimeValuesBuildItem.java b/extensions/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/ConversionMethodInfoRuntimeValuesBuildItem.java
new file mode 100644
index 00000000000..31cc899015d
--- /dev/null
+++ b/extensions/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/ConversionMethodInfoRuntimeValuesBuildItem.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.deployment;
+
+import java.util.Set;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+import io.quarkus.runtime.RuntimeValue;
+import org.apache.camel.quarkus.component.mapstruct.ConversionMethodInfo;
+
+/**
+ * Holds info about generated TypeConverter ConversionMethod.
+ */
+public final class ConversionMethodInfoRuntimeValuesBuildItem extends SimpleBuildItem {
+ private final Set> conversionMethodInfoRuntimeValues;
+
+ public ConversionMethodInfoRuntimeValuesBuildItem(
+ Set> conversionMethodInfoRuntimeValues) {
+ this.conversionMethodInfoRuntimeValues = conversionMethodInfoRuntimeValues;
+ }
+
+ public Set> getConversionMethodInfoRuntimeValues() {
+ return conversionMethodInfoRuntimeValues;
+ }
+}
diff --git a/extensions/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/MapStructMapperPackagesBuildItem.java b/extensions/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/MapStructMapperPackagesBuildItem.java
new file mode 100644
index 00000000000..ac77a04dbfc
--- /dev/null
+++ b/extensions/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/MapStructMapperPackagesBuildItem.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.deployment;
+
+import java.util.Set;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+
+/**
+ * Holds the set of discovered MapStruct Mapper packages.
+ */
+public final class MapStructMapperPackagesBuildItem extends SimpleBuildItem {
+ private final Set mapperPackages;
+
+ public MapStructMapperPackagesBuildItem(Set mapperPackages) {
+ this.mapperPackages = mapperPackages;
+ }
+
+ public Set getMapperPackages() {
+ return mapperPackages;
+ }
+}
diff --git a/extensions/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/MapStructProcessor.java b/extensions/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/MapStructProcessor.java
new file mode 100644
index 00000000000..d60b75d3c19
--- /dev/null
+++ b/extensions/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/MapStructProcessor.java
@@ -0,0 +1,374 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.deployment;
+
+import java.lang.reflect.Modifier;
+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 java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
+
+import io.quarkus.arc.deployment.BeanContainerBuildItem;
+import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
+import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
+import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
+import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
+import io.quarkus.gizmo.ClassCreator;
+import io.quarkus.gizmo.ClassOutput;
+import io.quarkus.gizmo.FieldCreator;
+import io.quarkus.gizmo.FieldDescriptor;
+import io.quarkus.gizmo.MethodCreator;
+import io.quarkus.gizmo.MethodDescriptor;
+import io.quarkus.gizmo.ResultHandle;
+import io.quarkus.runtime.RuntimeValue;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import jakarta.inject.Singleton;
+import org.apache.camel.Exchange;
+import org.apache.camel.component.mapstruct.MapstructComponent;
+import org.apache.camel.quarkus.component.mapstruct.ConversionMethodInfo;
+import org.apache.camel.quarkus.component.mapstruct.MapStructRecorder;
+import org.apache.camel.quarkus.core.deployment.spi.CamelBeanBuildItem;
+import org.apache.camel.quarkus.core.deployment.spi.CamelTypeConverterRegistryBuildItem;
+import org.apache.camel.support.SimpleTypeConverter.ConversionMethod;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.ReflectionHelper;
+import org.apache.camel.util.StringHelper;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationTarget;
+import org.jboss.jandex.AnnotationValue;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.FieldInfo;
+import org.jboss.jandex.IndexView;
+import org.mapstruct.Mapper;
+
+class MapStructProcessor {
+
+ private static final String FEATURE = "camel-mapstruct";
+
+ @BuildStep
+ FeatureBuildItem feature() {
+ return new FeatureBuildItem(FEATURE);
+ }
+
+ @BuildStep
+ MapStructMapperPackagesBuildItem getMapperPackages(CombinedIndexBuildItem combinedIndex) {
+ final Set mapperPackages = new HashSet<>();
+
+ Optional mapperPackageName = ConfigProvider.getConfig()
+ .getOptionalValue("camel.component.mapstruct.mapper-package-name", String.class);
+
+ if (mapperPackageName.isPresent()) {
+ String packages = StringUtils.deleteWhitespace(mapperPackageName.get());
+ mapperPackages.addAll(Arrays.asList(packages.split(",")));
+ } else {
+ // Fallback on auto discovery
+ combinedIndex.getIndex()
+ .getAnnotations(Mapper.class)
+ .stream()
+ .map(AnnotationInstance::target)
+ .map(AnnotationTarget::asClass)
+ .map(ClassInfo::name)
+ .map(DotName::packagePrefix)
+ .forEach(mapperPackages::add);
+ }
+
+ return new MapStructMapperPackagesBuildItem(mapperPackages);
+ }
+
+ @Record(ExecutionTime.STATIC_INIT)
+ @BuildStep
+ CamelBeanBuildItem mapStructComponentBean(
+ MapStructMapperPackagesBuildItem mapperPackages,
+ ConversionMethodInfoRuntimeValuesBuildItem conversionMethodInfos,
+ MapStructRecorder recorder) {
+ return new CamelBeanBuildItem("mapstruct", MapstructComponent.class.getName(),
+ recorder.createMapStructComponent(mapperPackages.getMapperPackages(),
+ conversionMethodInfos.getConversionMethodInfoRuntimeValues()));
+ }
+
+ @Record(ExecutionTime.STATIC_INIT)
+ @BuildStep
+ void generateMapStructTypeConverters(
+ BuildProducer generatedBean,
+ BuildProducer generatedClass,
+ BuildProducer unremovableBean,
+ BuildProducer reflectiveClass,
+ BuildProducer conversionMethodInfos,
+ CombinedIndexBuildItem combinedIndex,
+ MapStructMapperPackagesBuildItem mapperPackages,
+ MapStructRecorder recorder) {
+
+ // The logic that follows mimics dynamic TypeConverter logic in DefaultMapStructFinder.discoverMappings
+ Set packages = mapperPackages.getMapperPackages();
+ AtomicInteger methodCount = new AtomicInteger();
+ Map> conversionMethods = new HashMap<>();
+ IndexView index = combinedIndex.getIndex();
+
+ // Find implementations of Mapper annotated interfaces or abstract classes
+ index.getAnnotations(Mapper.class)
+ .stream()
+ .map(AnnotationInstance::target)
+ .map(AnnotationTarget::asClass)
+ .filter(classInfo -> packages.contains(classInfo.name().packagePrefix()))
+ .filter(classInfo -> classInfo.isInterface() || Modifier.isAbstract(classInfo.flags()))
+ .flatMap(classInfo -> Stream.concat(index.getAllKnownImplementors(classInfo.name()).stream(),
+ index.getAllKnownSubclasses(classInfo.name()).stream()))
+ .forEach(classInfo -> {
+ AtomicReference> mapperRuntimeValue = new AtomicReference<>();
+ String mapperClassName = classInfo.name().toString();
+ String mapperDefinitionClassName = getMapperDefinitionClassName(classInfo);
+ if (ObjectHelper.isEmpty(mapperDefinitionClassName)) {
+ return;
+ }
+
+ // Check if there's a static instance field defined for the Mapper
+ ClassInfo mapperDefinitionClassInfo = index.getClassByName(mapperDefinitionClassName);
+ Optional mapperInstanceField = mapperDefinitionClassInfo
+ .fields()
+ .stream()
+ .filter(fieldInfo -> Modifier.isStatic(fieldInfo.flags()))
+ .filter(fieldInfo -> fieldInfo.type().name().toString().equals(mapperDefinitionClassName))
+ .findFirst();
+
+ // Check of the Mapper is a CDI bean with one of the supported MapStruct annotations
+ boolean mapperBeanExists = classInfo.hasDeclaredAnnotation(ApplicationScoped.class)
+ || classInfo.hasDeclaredAnnotation(Named.class);
+ if (mapperInstanceField.isEmpty()) {
+ if (mapperBeanExists) {
+ unremovableBean
+ .produce(new UnremovableBeanBuildItem(beanInfo -> beanInfo.hasType(classInfo.name())));
+ } else {
+ // Create the Mapper ourselves
+ mapperRuntimeValue.set(recorder.createMapper(mapperClassName));
+ }
+ }
+
+ /*
+ * Generate SimpleTypeConverter.ConversionMethod implementations for each candidate Mapper method.
+ *
+ * ReflectionHelper is used to resolve the mapper methods for simplicity, compared to Jandex where
+ * we potentially have to iterate over the type hierarchy (E.g for multiple interfaces,
+ * interface / class inheritance etc).
+ *
+ * public final class FooConversionMethod implements ConversionMethod {
+ * private final FooMapperImpl mapper;
+ *
+ * // Generated only if a Mapper instance is a CDI bean
+ * public FooConversionMethod() {
+ * }
+ *
+ * // Generated only if a Mapper instance was declared on the Mapper interface
+ * public FooConversionMethod() {
+ * this(CarMapper.INSTANCE);
+ * }
+ *
+ * public FooConversionMethod(FooMapperImpl mapper) {
+ * this.mapper = mapper;
+ * }
+ *
+ * @Override
+ * public Object doConvert(Class> type, Exchange exchange, Object value) throws Exception {
+ * return mapper.stringToInt(value);
+ * }
+ * }
+ */
+ ReflectionHelper.doWithMethods(resolveClass(mapperDefinitionClassName), method -> {
+ Class>[] parameterTypes = method.getParameterTypes();
+ if (method.getParameterCount() != 1) {
+ return;
+ }
+
+ Class> fromType = parameterTypes[0];
+ Class> toType = method.getReturnType();
+ if (toType.isPrimitive()) {
+ return;
+ }
+
+ String conversionMethodClassName = String.format("%s.%s%dConversionMethod",
+ classInfo.name().packagePrefixName().toString(),
+ StringHelper.capitalize(method.getName()),
+ methodCount.incrementAndGet());
+
+ ClassOutput output = mapperBeanExists ? new GeneratedBeanGizmoAdaptor(generatedBean)
+ : new GeneratedClassGizmoAdaptor(generatedClass, true);
+
+ try (ClassCreator classCreator = ClassCreator.builder()
+ .className(conversionMethodClassName)
+ .classOutput(output)
+ .setFinal(true)
+ .interfaces(ConversionMethod.class)
+ .superClass(Object.class.getName())
+ .build()) {
+
+ // Take advantage of CDI and use injection to get the Mapper instance
+ if (mapperBeanExists) {
+ classCreator.addAnnotation(Singleton.class);
+ unremovableBean.produce(new UnremovableBeanBuildItem(
+ beanInfo -> beanInfo.hasType(DotName.createSimple(conversionMethodClassName))));
+ }
+
+ FieldCreator mapperField = classCreator.getFieldCreator("mapper", mapperClassName)
+ .setModifiers(Modifier.PRIVATE | Modifier.FINAL);
+
+ if (mapperInstanceField.isPresent() || mapperBeanExists) {
+ // Create a no-args constructor
+ try (MethodCreator initMethod = classCreator.getMethodCreator("", void.class)) {
+ initMethod.setModifiers(Modifier.PUBLIC);
+ if (mapperBeanExists) {
+ initMethod.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class),
+ initMethod.getThis());
+ } else {
+ // If we don't have CDI injection, get the Mapper instance from the Mapper interface
+ FieldInfo fieldInfo = mapperInstanceField.get();
+ initMethod.invokeSpecialMethod(
+ MethodDescriptor.ofConstructor(conversionMethodClassName,
+ mapperClassName),
+ initMethod.getThis(),
+ initMethod.readStaticField(FieldDescriptor.of(mapperClassName,
+ fieldInfo.name(), fieldInfo.type().toString())));
+ }
+ initMethod.returnNull();
+ }
+ }
+
+ try (MethodCreator initMethod = classCreator.getMethodCreator("", void.class,
+ mapperClassName)) {
+ initMethod.setModifiers(Modifier.PUBLIC);
+ if (mapperBeanExists) {
+ initMethod.addAnnotation(Inject.class);
+ }
+ initMethod.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class),
+ initMethod.getThis());
+ initMethod.writeInstanceField(mapperField.getFieldDescriptor(), initMethod.getThis(),
+ initMethod.getMethodParam(0));
+ initMethod.returnNull();
+ }
+
+ // doConvert implementation
+ try (MethodCreator doConvertMethod = classCreator.getMethodCreator("doConvert",
+ Object.class, Class.class, Exchange.class, Object.class)) {
+ doConvertMethod.setModifiers(Modifier.PUBLIC);
+
+ MethodDescriptor mapperMethod = MethodDescriptor.ofMethod(mapperClassName,
+ method.getName(), toType.getName(), fromType.getName());
+
+ ResultHandle mapper = doConvertMethod
+ .readInstanceField(mapperField.getFieldDescriptor(), doConvertMethod.getThis());
+
+ // Invoke the target method on the Mapper with the 'value' method arg from convertTo
+ ResultHandle mapperResult = doConvertMethod.invokeVirtualMethod(mapperMethod,
+ mapper, doConvertMethod.getMethodParam(2));
+
+ doConvertMethod.returnValue(mapperResult);
+ }
+ }
+
+ // Register the 'to' type for reflection (See MapstructEndpoint.doBuild())
+ reflectiveClass.produce(ReflectiveClassBuildItem.builder(toType).build());
+
+ // Instantiate the generated ConversionMethod
+ String key = String.format("%s:%s", fromType.getName(), toType.getName());
+ conversionMethods.computeIfAbsent(key,
+ conversionMethodsKey -> recorder.createConversionMethodInfo(fromType, toType,
+ mapperBeanExists,
+ mapperRuntimeValue.get(), conversionMethodClassName));
+ });
+ });
+
+ conversionMethodInfos
+ .produce(new ConversionMethodInfoRuntimeValuesBuildItem(new HashSet<>(conversionMethods.values())));
+ }
+
+ @Record(ExecutionTime.STATIC_INIT)
+ @BuildStep
+ void registerTypeConverters(
+ BeanContainerBuildItem beanContainer,
+ CamelTypeConverterRegistryBuildItem typeConverterRegistry,
+ ConversionMethodInfoRuntimeValuesBuildItem conversionMethodInfos,
+ MapStructRecorder recorder) {
+ // Register the TypeConverter leveraging the generated ConversionMethod
+ recorder.registerMapStructTypeConverters(typeConverterRegistry.getRegistry(),
+ conversionMethodInfos.getConversionMethodInfoRuntimeValues(), beanContainer.getValue());
+ }
+
+ @BuildStep
+ void registerMapperServiceProviders(
+ BuildProducer serviceProvider,
+ CombinedIndexBuildItem combinedIndex,
+ MapStructMapperPackagesBuildItem mapperPackages) {
+ // If a Mapper definition uses a custom implementationName then they may get loaded via the ServiceLoader
+ Set packages = mapperPackages.getMapperPackages();
+ combinedIndex.getIndex()
+ .getAnnotations(Mapper.class)
+ .stream()
+ .filter(annotationInstance -> packages.contains(annotationInstance.target().asClass().name().packagePrefix()))
+ .forEach(annotation -> {
+ AnnotationValue value = annotation.value("implementationName");
+ if (value != null) {
+ DotName name = annotation.target().asClass().name();
+ AnnotationValue implementationPackage = annotation.value("implementationPackage");
+ String packageName = implementationPackage != null ? implementationPackage.asString()
+ : name.packagePrefix();
+ serviceProvider
+ .produce(new ServiceProviderBuildItem(name.toString(), packageName + "." + value.asString()));
+ }
+ });
+ }
+
+ /**
+ * Gets the name of the Mapper interface or abstract class
+ */
+ private String getMapperDefinitionClassName(ClassInfo mapStructMapperImpl) {
+ List interfaces = mapStructMapperImpl.interfaceNames();
+ if (interfaces.isEmpty()) {
+ String superClassName = mapStructMapperImpl.superClassType().name().toString();
+ if (!superClassName.equals(Object.class.getName())) {
+ return superClassName;
+ }
+ return null;
+ }
+ return interfaces.get(0).toString();
+ }
+
+ private Class> resolveClass(String className) {
+ try {
+ return Class.forName(className, false, Thread.currentThread().getContextClassLoader());
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/extensions-jvm/mapstruct/pom.xml b/extensions/mapstruct/pom.xml
similarity index 96%
rename from extensions-jvm/mapstruct/pom.xml
rename to extensions/mapstruct/pom.xml
index 023d4eca551..4f14eb57665 100644
--- a/extensions-jvm/mapstruct/pom.xml
+++ b/extensions/mapstruct/pom.xml
@@ -23,7 +23,7 @@
4.0.0
org.apache.camel.quarkus
- camel-quarkus-extensions-jvm
+ camel-quarkus-extensions
3.0.0-SNAPSHOT
../pom.xml
diff --git a/extensions-jvm/mapstruct/runtime/pom.xml b/extensions/mapstruct/runtime/pom.xml
similarity index 98%
rename from extensions-jvm/mapstruct/runtime/pom.xml
rename to extensions/mapstruct/runtime/pom.xml
index fd1b388885f..171f1686402 100644
--- a/extensions-jvm/mapstruct/runtime/pom.xml
+++ b/extensions/mapstruct/runtime/pom.xml
@@ -34,6 +34,7 @@
3.0.0
+ 3.0.0
diff --git a/extensions/mapstruct/runtime/src/main/doc/usage.adoc b/extensions/mapstruct/runtime/src/main/doc/usage.adoc
new file mode 100644
index 00000000000..dad335f4c90
--- /dev/null
+++ b/extensions/mapstruct/runtime/src/main/doc/usage.adoc
@@ -0,0 +1,46 @@
+=== Annotation Processor
+
+To use MapStruct, you must configure your build to use an annotation processor.
+
+==== Maven
+
+[source,xml]
+----
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.mapstruct
+ mapstruct-processor
+ {mapstruct-version}
+
+
+
+
+
+----
+
+==== Gradle
+
+[source,gradle]
+----
+dependencies {
+ annotationProcessor 'org.mapstruct:mapstruct-processor:{mapstruct-version}'
+ testAnnotationProcessor 'org.mapstruct:mapstruct-processor:{mapstruct-version}'
+}
+----
+
+=== Mapper definition discovery
+
+By default, {project-name} will automatically discover the package paths of your `@Mapper` annotated interfaces or abstract classes and
+pass them to the Camel MapStruct component.
+
+If you want finer control over the specific packages that are scanned, then you can set a configuration property in `application.properties`.
+
+[source,properties]
+----
+camel.component.mapstruct.mapper-package-name = com.first.package,org.second.package
+----
diff --git a/extensions/mapstruct/runtime/src/main/java/org/apache/camel/quarkus/component/mapstruct/CamelQuarkusMapStructMapperFinder.java b/extensions/mapstruct/runtime/src/main/java/org/apache/camel/quarkus/component/mapstruct/CamelQuarkusMapStructMapperFinder.java
new file mode 100644
index 00000000000..e1914f4c513
--- /dev/null
+++ b/extensions/mapstruct/runtime/src/main/java/org/apache/camel/quarkus/component/mapstruct/CamelQuarkusMapStructMapperFinder.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct;
+
+import org.apache.camel.component.mapstruct.MapStructMapperFinder;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.ObjectHelper;
+import org.jboss.logging.Logger;
+
+/**
+ * Custom {@link MapStructMapperFinder} that is effectively a noop implementation, as the work of discovering
+ * mappings is done at build time.
+ */
+public class CamelQuarkusMapStructMapperFinder extends ServiceSupport implements MapStructMapperFinder {
+ private static final Logger LOG = Logger.getLogger(CamelQuarkusMapStructMapperFinder.class);
+
+ private final int mappingsCount;
+ private String mapperPackageName;
+
+ public CamelQuarkusMapStructMapperFinder(String mapperPackageName, int mappingsCount) {
+ setMapperPackageName(mapperPackageName);
+ this.mappingsCount = mappingsCount;
+ }
+
+ @Override
+ public void setMapperPackageName(String mapperPackageName) {
+ this.mapperPackageName = mapperPackageName;
+ }
+
+ @Override
+ public String getMapperPackageName() {
+ return this.mapperPackageName;
+ }
+
+ @Override
+ public int discoverMappings(Class> clazz) {
+ // Discovery is done at build time so just return the count
+ return mappingsCount;
+ }
+
+ @Override
+ protected void doInit() throws Exception {
+ if (ObjectHelper.isNotEmpty(mapperPackageName)) {
+ LOG.infof("Discovered %d MapStruct type converters during build time augmentation: %s", mappingsCount,
+ mapperPackageName);
+ }
+ }
+}
diff --git a/extensions/mapstruct/runtime/src/main/java/org/apache/camel/quarkus/component/mapstruct/ConversionMethodInfo.java b/extensions/mapstruct/runtime/src/main/java/org/apache/camel/quarkus/component/mapstruct/ConversionMethodInfo.java
new file mode 100644
index 00000000000..dc6d6bb16c1
--- /dev/null
+++ b/extensions/mapstruct/runtime/src/main/java/org/apache/camel/quarkus/component/mapstruct/ConversionMethodInfo.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct;
+
+import java.util.Objects;
+
+import io.quarkus.runtime.RuntimeValue;
+
+/**
+ * Holds configuration for dynamic TypeConverter instantiation and registration.
+ */
+public class ConversionMethodInfo {
+ private final Class> fromClass;
+ private final Class> toClass;
+ private final boolean cdiBean;
+ private final RuntimeValue> mapper;
+ private final String conversionMethodClassName;
+
+ public ConversionMethodInfo(
+ Class> fromClass,
+ Class> toClass,
+ boolean cdiBean,
+ RuntimeValue> mapper,
+ String conversionMethodClassName) {
+ this.fromClass = fromClass;
+ this.toClass = toClass;
+ this.cdiBean = cdiBean;
+ this.mapper = mapper;
+ this.conversionMethodClassName = conversionMethodClassName;
+ }
+
+ public Class> getFromClass() {
+ return fromClass;
+ }
+
+ public Class> getToClass() {
+ return toClass;
+ }
+
+ public boolean isCdiBean() {
+ return cdiBean;
+ }
+
+ public RuntimeValue> getMapper() {
+ return mapper;
+ }
+
+ public String getConversionMethodClassName() {
+ return conversionMethodClassName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ConversionMethodInfo that = (ConversionMethodInfo) o;
+ return cdiBean == that.cdiBean && Objects.equals(fromClass, that.fromClass) && Objects.equals(toClass, that.toClass)
+ && Objects.equals(mapper, that.mapper)
+ && Objects.equals(conversionMethodClassName, that.conversionMethodClassName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fromClass, toClass, cdiBean, mapper, conversionMethodClassName);
+ }
+}
diff --git a/extensions/mapstruct/runtime/src/main/java/org/apache/camel/quarkus/component/mapstruct/MapStructRecorder.java b/extensions/mapstruct/runtime/src/main/java/org/apache/camel/quarkus/component/mapstruct/MapStructRecorder.java
new file mode 100644
index 00000000000..fab7e73e96e
--- /dev/null
+++ b/extensions/mapstruct/runtime/src/main/java/org/apache/camel/quarkus/component/mapstruct/MapStructRecorder.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Set;
+
+import io.quarkus.arc.runtime.BeanContainer;
+import io.quarkus.runtime.RuntimeValue;
+import io.quarkus.runtime.annotations.Recorder;
+import org.apache.camel.component.mapstruct.MapstructComponent;
+import org.apache.camel.spi.TypeConverterRegistry;
+import org.apache.camel.support.SimpleTypeConverter;
+import org.apache.camel.support.SimpleTypeConverter.ConversionMethod;
+
+@Recorder
+public class MapStructRecorder {
+
+ public RuntimeValue> createMapper(String mapperClassName) {
+ try {
+ Object mapper = Thread.currentThread()
+ .getContextClassLoader()
+ .loadClass(mapperClassName)
+ .getDeclaredConstructor()
+ .newInstance();
+ return new RuntimeValue<>(mapper);
+ } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException
+ | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public RuntimeValue createConversionMethodInfo(
+ Class> from,
+ Class> to,
+ boolean cdiBean,
+ RuntimeValue> mapper,
+ String conversionMethodClassName) {
+ return new RuntimeValue<>(
+ new ConversionMethodInfo(from, to, cdiBean, mapper, conversionMethodClassName));
+ }
+
+ public RuntimeValue createMapStructComponent(
+ Set mapperPackages,
+ Set> conversionMethodInfos) {
+ String packages = String.join(",", mapperPackages);
+ CamelQuarkusMapStructMapperFinder finder = new CamelQuarkusMapStructMapperFinder(packages,
+ conversionMethodInfos.size());
+ MapstructComponent component = new MapstructComponent();
+ component.setMapperPackageName(packages);
+ component.setMapStructConverter(finder);
+ return new RuntimeValue<>(component);
+ }
+
+ public void registerMapStructTypeConverters(
+ RuntimeValue typeConverterRegistryRuntimeValue,
+ Set> conversionMethods,
+ BeanContainer container) {
+ TypeConverterRegistry registry = typeConverterRegistryRuntimeValue.getValue();
+ conversionMethods.forEach(c -> {
+ try {
+ ConversionMethodInfo info = c.getValue();
+
+ // Create the ConversionMethod for the SimpleTypeConverter
+ Object conversionMethod;
+ Class> conversionMethodClass = Thread.currentThread().getContextClassLoader()
+ .loadClass(info.getConversionMethodClassName());
+
+ if (info.isCdiBean()) {
+ conversionMethod = container.beanInstance(conversionMethodClass);
+ } else if (info.getMapper() != null) {
+ // Pass the Mapper instance created at build time
+ Object mapper = info.getMapper().getValue();
+ conversionMethod = conversionMethodClass.getDeclaredConstructor(mapper.getClass()).newInstance(mapper);
+ } else {
+ // Default no-args constructor uses a Mapper instance declared in the Mapper interface
+ conversionMethod = conversionMethodClass.getDeclaredConstructor().newInstance();
+ }
+
+ registry.addTypeConverter(info.getToClass(), info.getFromClass(),
+ new SimpleTypeConverter(false, (ConversionMethod) conversionMethod));
+ } catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException
+ | NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+}
diff --git a/extensions-jvm/mapstruct/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/mapstruct/runtime/src/main/resources/META-INF/quarkus-extension.yaml
similarity index 100%
rename from extensions-jvm/mapstruct/runtime/src/main/resources/META-INF/quarkus-extension.yaml
rename to extensions/mapstruct/runtime/src/main/resources/META-INF/quarkus-extension.yaml
diff --git a/extensions/pom.xml b/extensions/pom.xml
index bf80304b2ed..c8262d67047 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -169,6 +169,7 @@
lzf
mail
management
+ mapstruct
master
micrometer
microprofile-fault-tolerance
diff --git a/integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructResource.java b/integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructResource.java
deleted file mode 100644
index 7bad4fdf110..00000000000
--- a/integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructResource.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it;
-
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.core.Response;
-import org.apache.camel.ProducerTemplate;
-import org.apache.camel.quarkus.component.mapstruct.it.model.Car;
-import org.apache.camel.quarkus.component.mapstruct.it.model.Vehicle;
-
-@Path("/mapstruct")
-@ApplicationScoped
-public class MapStructResource {
- @Inject
- ProducerTemplate producerTemplate;
-
- @Path("/component")
- @POST
- @Consumes("text/plain")
- @Produces("text/plain")
- public Response componentTest(String vehicleString) {
- return Response.ok(testMapping("component", vehicleString)).build();
- }
-
- @Path("/converter")
- @POST
- @Consumes("text/plain")
- @Produces("text/plain")
- public Response converterTest(String vehicleString) {
- return Response.ok(testMapping("converter", vehicleString)).build();
- }
-
- private String testMapping(String endpoint, String vehicleString) {
- return producerTemplate.requestBody("direct:" + endpoint, Vehicle.fromString(vehicleString), Car.class).toString();
- }
-}
diff --git a/integration-tests-jvm/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructTest.java b/integration-tests-jvm/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructTest.java
deleted file mode 100644
index c748bbe4351..00000000000
--- a/integration-tests-jvm/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it;
-
-import io.quarkus.test.junit.QuarkusTest;
-import io.restassured.RestAssured;
-import org.apache.camel.quarkus.component.mapstruct.it.model.Car;
-import org.apache.camel.quarkus.component.mapstruct.it.model.Vehicle;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-@QuarkusTest
-public class MapStructTest {
- private static final Vehicle VEHICLE = new Vehicle("Volvo", "XC60", "true", 2021);
-
- @ParameterizedTest
- @ValueSource(strings = { "component", "converter" })
- public void testMapping(String value) {
- String response = RestAssured.given()
- .body(VEHICLE.toString())
- .post("/mapstruct/" + value)
- .then()
- .statusCode(200)
- .extract().body().asString();
-
- Car car = Car.fromString(response);
-
- assertEquals(car.getBrand(), VEHICLE.getCompany());
- assertEquals(car.getModel(), VEHICLE.getName());
- assertEquals(car.getYear(), VEHICLE.getYear());
- assertEquals(car.isElectric(), Boolean.parseBoolean(VEHICLE.getPower()));
- }
-}
diff --git a/integration-tests-jvm/pom.xml b/integration-tests-jvm/pom.xml
index 1ba33ea06db..f512624870f 100644
--- a/integration-tests-jvm/pom.xml
+++ b/integration-tests-jvm/pom.xml
@@ -82,7 +82,6 @@
jt400
ldif
lucene
- mapstruct
mvel
printer
pulsar
diff --git a/integration-tests-jvm/mapstruct/pom.xml b/integration-tests/mapstruct/pom.xml
similarity index 83%
rename from integration-tests-jvm/mapstruct/pom.xml
rename to integration-tests/mapstruct/pom.xml
index 0b23d5437e2..b84bb2e1f81 100644
--- a/integration-tests-jvm/mapstruct/pom.xml
+++ b/integration-tests/mapstruct/pom.xml
@@ -60,6 +60,24 @@
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${mapstruct.version}
+
+
+
+
+
+
+
virtualDependencies
@@ -98,23 +116,32 @@
+
+ native
+
+
+ native
+
+
+
+ native
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+
+
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
-
- org.mapstruct
- mapstruct-processor
- ${mapstruct.version}
-
-
-
-
-
-
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructResource.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructResource.java
new file mode 100644
index 00000000000..8d7971bb8bc
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructResource.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it;
+
+import java.util.Objects;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.apache.camel.CamelContext;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.component.mapstruct.MapStructMapperFinder;
+import org.apache.camel.component.mapstruct.MapstructComponent;
+import org.apache.camel.quarkus.component.mapstruct.it.model.ModelFactory;
+import org.apache.camel.util.StringHelper;
+import org.jboss.logging.Logger;
+
+@Path("/mapstruct")
+@ApplicationScoped
+public class MapStructResource {
+ private static final Logger LOG = Logger.getLogger(MapStructResource.class);
+
+ @Inject
+ ProducerTemplate producerTemplate;
+
+ @Inject
+ CamelContext context;
+
+ @Path("/component")
+ @POST
+ @Consumes("text/plain")
+ @Produces("text/plain")
+ public Response componentTest(
+ @QueryParam("fromTypeName") String fromTypeName,
+ @QueryParam("toTypeName") String toTypeName,
+ String pojoString) {
+ try {
+ String result = doMapping(fromTypeName, toTypeName, "component", pojoString);
+ return Response.ok(result).build();
+ } catch (Exception e) {
+ LOG.error("Error occurred during mapping", e);
+ String message = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
+ return Response.serverError().entity(message).build();
+ }
+ }
+
+ @Path("/converter")
+ @POST
+ @Consumes("text/plain")
+ @Produces("text/plain")
+ public Response converterTest(
+ @QueryParam("fromTypeName") String fromTypeName,
+ @QueryParam("toTypeName") String toTypeName,
+ String pojoString) {
+ try {
+ String result = doMapping(fromTypeName, toTypeName, "converter", pojoString);
+ return Response.ok(result).build();
+ } catch (Exception e) {
+ LOG.error("Error occurred during mapping", e);
+ String message = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
+ return Response.serverError().entity(message).build();
+ }
+ }
+
+ @Path("/finder/mapper")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String mapStructMapperFinderImpl() {
+ MapstructComponent component = context.getComponent("mapstruct", MapstructComponent.class);
+ MapStructMapperFinder mapStructConverter = component.getMapStructConverter();
+ Objects.requireNonNull(mapStructConverter, "mapStructConverter should not be null");
+ return mapStructConverter.getClass().getName();
+ }
+
+ @Path("/component/packages")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String mapStructComponentPackages() {
+ MapstructComponent component = context.getComponent("mapstruct", MapstructComponent.class);
+ return component.getMapperPackageName();
+ }
+
+ private String doMapping(String fromType, String toType, String endpoint, String pojoString) {
+ Object pojo = ModelFactory.getModel(pojoString, fromType);
+ String toTypeHeader = endpoint.equals("component") ? toType : StringHelper.afterLast(toType.toLowerCase(), ".");
+ return producerTemplate.requestBodyAndHeader("direct:" + endpoint, pojo, "toType", toTypeHeader).toString();
+ }
+}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructRoutes.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructRoutes.java
new file mode 100644
index 00000000000..622ab3c207a
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructRoutes.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Bike;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Car;
+import org.apache.camel.quarkus.component.mapstruct.it.model.CarDto;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Cat;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Dog;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Employee;
+import org.apache.camel.quarkus.component.mapstruct.it.model.EmployeeDto;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Vehicle;
+
+public class MapStructRoutes extends RouteBuilder {
+ @Override
+ public void configure() throws Exception {
+ from("direct:component")
+ .toD("mapstruct:${header.toType}");
+
+ from("direct:converter")
+ .choice()
+ .when(simple("${header.toType} == 'bike'"))
+ .convertBodyTo(Bike.class)
+ .when(simple("${header.toType} == 'car'"))
+ .convertBodyTo(Car.class)
+ .when(simple("${header.toType} == 'cardto'"))
+ .convertBodyTo(CarDto.class)
+ .when(simple("${header.toType} == 'cat'"))
+ .convertBodyTo(Cat.class)
+ .when(simple("${header.toType} == 'dog'"))
+ .convertBodyTo(Dog.class)
+ .when(simple("${header.toType} == 'employee'"))
+ .convertBodyTo(Employee.class)
+ .when(simple("${header.toType} == 'employeedto'"))
+ .convertBodyTo(EmployeeDto.class)
+ .when(simple("${header.toType} == 'vehicle'"))
+ .convertBodyTo(Vehicle.class);
+ }
+}
diff --git a/integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/CarMapper.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/car/CarMapper.java
similarity index 79%
rename from integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/CarMapper.java
rename to integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/car/CarMapper.java
index 0a7f236df2f..d372532cf37 100644
--- a/integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/CarMapper.java
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/car/CarMapper.java
@@ -14,8 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.mapstruct.it.mapper;
+package org.apache.camel.quarkus.component.mapstruct.it.mapper.car;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Bike;
import org.apache.camel.quarkus.component.mapstruct.it.model.Car;
import org.apache.camel.quarkus.component.mapstruct.it.model.Vehicle;
import org.mapstruct.Mapper;
@@ -27,4 +28,9 @@ public interface CarMapper {
@Mapping(source = "name", target = "model")
@Mapping(source = "power", target = "electric")
Car toCar(Vehicle vehicle);
+
+ @Mapping(source = "make", target = "brand")
+ @Mapping(source = "modelNumber", target = "model")
+ @Mapping(source = "electric", target = "electric")
+ Car toCar(Bike bike);
}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/cat/CatMapper.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/cat/CatMapper.java
new file mode 100644
index 00000000000..d78cf081937
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/cat/CatMapper.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.mapper.cat;
+
+import org.apache.camel.quarkus.component.mapstruct.it.model.Cat;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Dog;
+import org.mapstruct.Mapper;
+
+// Test with alternative class and package names (will be instantiated via ServiceLoader)
+@Mapper(implementationName = "CamelQuarkusCatMapper", implementationPackage = "org.test.mapper")
+public interface CatMapper {
+ // Test hand-written mapping logic
+ default Cat dogToCat(Dog dog) {
+ return new Cat(dog.getDogId(), dog.getName(), dog.getAge(), "meow");
+ }
+}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/dog/DogMapper.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/dog/DogMapper.java
new file mode 100644
index 00000000000..a0595f47e94
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/dog/DogMapper.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.mapper.dog;
+
+import java.util.UUID;
+
+import org.apache.camel.quarkus.component.mapstruct.it.model.Cat;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Dog;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingConstants;
+
+// Test CDI support for ApplicationScope beans
+@Mapper(imports = UUID.class, componentModel = MappingConstants.ComponentModel.JAKARTA_CDI)
+public interface DogMapper {
+ // Test expressions
+ @Mapping(target = "dogId", source = "catId", defaultExpression = "java( UUID.randomUUID().toString() )")
+ @Mapping(source = "name", target = "name")
+ @Mapping(source = "age", target = "age")
+ @Mapping(target = "vocalization", constant = "bark")
+ Dog catToDog(Cat cat);
+}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/employee/EmployeeMapper.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/employee/EmployeeMapper.java
new file mode 100644
index 00000000000..01acfa42242
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/employee/EmployeeMapper.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.mapper.employee;
+
+import org.apache.camel.quarkus.component.mapstruct.it.model.Employee;
+import org.apache.camel.quarkus.component.mapstruct.it.model.EmployeeDto;
+import org.mapstruct.Mapper;
+
+@Mapper(implementationName = "CamelQuarkusEmployeeMapper")
+public abstract class EmployeeMapper extends EmployeeMapperBase {
+ // Verify hand-written mapping logic
+ public EmployeeDto employeeToemployeeDto(Employee employee) {
+ EmployeeDto dto = new EmployeeDto();
+ dto.setEmployeeId(employee.getId());
+ dto.setEmployeeName(employee.getName());
+ return dto;
+ }
+}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/employee/EmployeeMapperBase.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/employee/EmployeeMapperBase.java
new file mode 100644
index 00000000000..55f15d522c9
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/employee/EmployeeMapperBase.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.mapper.employee;
+
+import org.apache.camel.quarkus.component.mapstruct.it.model.Employee;
+import org.apache.camel.quarkus.component.mapstruct.it.model.EmployeeDto;
+
+public abstract class EmployeeMapperBase {
+ // Verify mapper method inheritance
+ public Employee employeeDtoToEmployee(EmployeeDto employeeDto) {
+ Employee employee = new Employee();
+ employee.setId(employeeDto.getEmployeeId());
+ employee.setName(employeeDto.getEmployeeName());
+ return employee;
+ }
+}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/vehicle/VehicleMapper.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/vehicle/VehicleMapper.java
new file mode 100644
index 00000000000..7097189cda4
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/mapper/vehicle/VehicleMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.mapper.vehicle;
+
+import org.apache.camel.quarkus.component.mapstruct.it.model.Car;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Vehicle;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public abstract class VehicleMapper {
+ // Test static Mapper field. It will be used by the generated TypeConverter
+ public static final VehicleMapper MAPPER = Mappers.getMapper(VehicleMapper.class);
+
+ @Mapping(source = "brand", target = "company")
+ @Mapping(source = "model", target = "name")
+ @Mapping(source = "electric", target = "power")
+ public abstract Vehicle carToVehicle(Car car);
+}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Bike.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Bike.java
new file mode 100644
index 00000000000..760c8b892b8
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Bike.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.model;
+
+public class Bike {
+ private String make;
+ private String modelNumber;
+ private int year;
+ private boolean electric;
+
+ public Bike(String make, String modelNumber, int year, boolean electric) {
+ this.make = make;
+ this.modelNumber = modelNumber;
+ this.year = year;
+ this.electric = electric;
+ }
+
+ public String getMake() {
+ return make;
+ }
+
+ public void setMake(String make) {
+ this.make = make;
+ }
+
+ public String getModelNumber() {
+ return modelNumber;
+ }
+
+ public void setModelNumber(String modelNumber) {
+ this.modelNumber = modelNumber;
+ }
+
+ public int getYear() {
+ return year;
+ }
+
+ public void setYear(int year) {
+ this.year = year;
+ }
+
+ public boolean isElectric() {
+ return electric;
+ }
+
+ public void setElectric(boolean electric) {
+ this.electric = electric;
+ }
+
+ @Override
+ public String toString() {
+ return String.join(",", make, modelNumber, String.valueOf(year), String.valueOf(electric));
+ }
+
+ public static Bike fromString(String bikeString) {
+ final String[] split = bikeString.split(",");
+ return new Bike(split[0], split[1], Integer.parseInt(split[2]), Boolean.parseBoolean(split[3]));
+ }
+}
diff --git a/integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Car.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Car.java
similarity index 95%
rename from integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Car.java
rename to integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Car.java
index 09eda2f054f..8dbb4ed3409 100644
--- a/integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Car.java
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Car.java
@@ -63,7 +63,7 @@ public void setElectric(boolean electric) {
@Override
public String toString() {
- return String.join(",", brand, model, year + "", electric + "");
+ return String.join(",", brand, model, String.valueOf(year), String.valueOf(electric));
}
public static Car fromString(String carString) {
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/CarDto.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/CarDto.java
new file mode 100644
index 00000000000..73a61f4c5e7
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/CarDto.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.model;
+
+public class CarDto {
+ private String brandName;
+ private String modelName;
+ private int year;
+ private boolean electric;
+
+ public CarDto(String brandName, String modelName, int year, boolean electric) {
+ this.brandName = brandName;
+ this.modelName = modelName;
+ this.year = year;
+ this.electric = electric;
+ }
+
+ public String getBrandName() {
+ return brandName;
+ }
+
+ public void setBrandName(String brandName) {
+ this.brandName = brandName;
+ }
+
+ public String getModelName() {
+ return modelName;
+ }
+
+ public void setModelName(String modelName) {
+ this.modelName = modelName;
+ }
+
+ public int getYear() {
+ return year;
+ }
+
+ public void setYear(int year) {
+ this.year = year;
+ }
+
+ public boolean isElectric() {
+ return electric;
+ }
+
+ public void setElectric(boolean electric) {
+ this.electric = electric;
+ }
+
+ @Override
+ public String toString() {
+ return String.join(",", brandName, modelName, String.valueOf(year), String.valueOf(electric));
+ }
+
+ public static CarDto fromString(String carDtoString) {
+ final String[] split = carDtoString.split(",");
+ return new CarDto(split[0], split[1], Integer.parseInt(split[2]), Boolean.parseBoolean(split[3]));
+ }
+}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Cat.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Cat.java
new file mode 100644
index 00000000000..6222aafed63
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Cat.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.model;
+
+public class Cat {
+ private String catId;
+ private String name;
+ private int age;
+ private String sound;
+
+ public Cat(String catId, String name, int age, String sound) {
+ this.catId = catId;
+ this.name = name;
+ this.age = age;
+ this.sound = sound;
+ }
+
+ public String getCatId() {
+ return catId;
+ }
+
+ public void setCatId(String catId) {
+ this.catId = catId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public String getSound() {
+ return sound;
+ }
+
+ public void setSound(String sound) {
+ this.sound = sound;
+ }
+
+ @Override
+ public String toString() {
+ return String.join(",", catId, name, String.valueOf(age), sound);
+ }
+
+ public static Cat fromString(String catString) {
+ final String[] split = catString.split(",");
+ return new Cat(split[0], split[1], Integer.parseInt(split[2]), split[3]);
+ }
+}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Dog.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Dog.java
new file mode 100644
index 00000000000..1ee8496b6a8
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Dog.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.model;
+
+public class Dog {
+ private String dogId;
+ private String name;
+ private int age;
+ private String vocalization;
+
+ public Dog(String dogId, String name, int age, String vocalization) {
+ this.dogId = dogId;
+ this.name = name;
+ this.age = age;
+ this.vocalization = vocalization;
+ }
+
+ public String getDogId() {
+ return dogId;
+ }
+
+ public void setDogId(String dogId) {
+ this.dogId = dogId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public String getVocalization() {
+ return vocalization;
+ }
+
+ public void setVocalization(String vocalization) {
+ this.vocalization = vocalization;
+ }
+
+ @Override
+ public String toString() {
+ return String.join(",", dogId, name, String.valueOf(age), vocalization);
+ }
+
+ public static Dog fromString(String dogString) {
+ final String[] split = dogString.split(",");
+ return new Dog(split[0], split[1], Integer.parseInt(split[2]), split[3]);
+ }
+}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Employee.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Employee.java
new file mode 100644
index 00000000000..e9a3ed57ef9
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Employee.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.model;
+
+public class Employee {
+ private int id;
+ private String name;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return String.join(",", String.valueOf(id), name);
+ }
+
+ public static Employee fromString(String employeeString) {
+ final String[] split = employeeString.split(",");
+ Employee employee = new Employee();
+ employee.setId(Integer.parseInt(split[0]));
+ employee.setName(split[1]);
+ return employee;
+ }
+}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/EmployeeDto.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/EmployeeDto.java
new file mode 100644
index 00000000000..cb93a8f1d17
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/EmployeeDto.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.model;
+
+public class EmployeeDto {
+ private int employeeId;
+ private String employeeName;
+
+ public int getEmployeeId() {
+ return employeeId;
+ }
+
+ public void setEmployeeId(int employeeId) {
+ this.employeeId = employeeId;
+ }
+
+ public String getEmployeeName() {
+ return employeeName;
+ }
+
+ public void setEmployeeName(String employeeName) {
+ this.employeeName = employeeName;
+ }
+
+ @Override
+ public String toString() {
+ return String.join(",", String.valueOf(employeeId), employeeName);
+ }
+
+ public static EmployeeDto fromString(String employeeDtoString) {
+ final String[] split = employeeDtoString.split(",");
+ EmployeeDto employee = new EmployeeDto();
+ employee.setEmployeeId(Integer.parseInt(split[0]));
+ employee.setEmployeeName(split[1]);
+ return employee;
+ }
+}
diff --git a/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/ModelFactory.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/ModelFactory.java
new file mode 100644
index 00000000000..f1f0803f1e7
--- /dev/null
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/ModelFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it.model;
+
+public class ModelFactory {
+
+ public static Object getModel(String modelString, String modelClassName) {
+ if (Bike.class.getName().equals(modelClassName)) {
+ return Bike.fromString(modelString);
+ } else if (Car.class.getName().equals(modelClassName)) {
+ return Car.fromString(modelString);
+ } else if (CarDto.class.getName().equals(modelClassName)) {
+ return CarDto.fromString(modelString);
+ } else if (Cat.class.getName().equals(modelClassName)) {
+ return Cat.fromString(modelString);
+ } else if (Dog.class.getName().equals(modelClassName)) {
+ return Dog.fromString(modelString);
+ } else if (Employee.class.getName().equals(modelClassName)) {
+ return Employee.fromString(modelString);
+ } else if (EmployeeDto.class.getName().equals(modelClassName)) {
+ return EmployeeDto.fromString(modelString);
+ } else if (Vehicle.class.getName().equals(modelClassName)) {
+ return Vehicle.fromString(modelString);
+ }
+ throw new IllegalArgumentException("Unknown model class: " + modelClassName);
+ }
+}
diff --git a/integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Vehicle.java b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Vehicle.java
similarity index 96%
rename from integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Vehicle.java
rename to integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Vehicle.java
index 22566c9c347..27e27449f08 100644
--- a/integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Vehicle.java
+++ b/integration-tests/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/model/Vehicle.java
@@ -63,7 +63,7 @@ public void setYear(int year) {
@Override
public String toString() {
- return String.join(",", company, name, power, year + "");
+ return String.join(",", company, name, power, String.valueOf(year));
}
public static Vehicle fromString(String vehicleString) {
diff --git a/integration-tests/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructExplicitPackagesTest.java b/integration-tests/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructExplicitPackagesTest.java
new file mode 100644
index 00000000000..1586a542615
--- /dev/null
+++ b/integration-tests/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructExplicitPackagesTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import io.restassured.RestAssured;
+import org.apache.camel.quarkus.component.mapstruct.it.mapper.car.CarMapper;
+import org.apache.camel.quarkus.component.mapstruct.it.mapper.vehicle.VehicleMapper;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Car;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Employee;
+import org.apache.camel.quarkus.component.mapstruct.it.model.EmployeeDto;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Vehicle;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@QuarkusTest
+@TestProfile(MapStructExplicitPackagesTestProfile.class)
+public class MapStructExplicitPackagesTest {
+ @ParameterizedTest
+ @ValueSource(strings = { "component", "converter" })
+ void mapVehicleToCarSuccess(String value) {
+ Vehicle vehicle = new Vehicle("Volvo", "XC60", "true", 2021);
+
+ String response = RestAssured.given()
+ .queryParam("fromTypeName", Vehicle.class.getName())
+ .queryParam("toTypeName", Car.class.getName())
+ .body(vehicle.toString())
+ .post("/mapstruct/" + value)
+ .then()
+ .statusCode(200)
+ .extract()
+ .body()
+ .asString();
+
+ Car car = Car.fromString(response);
+
+ assertEquals(vehicle.getCompany(), car.getBrand());
+ assertEquals(vehicle.getName(), car.getModel());
+ assertEquals(vehicle.getYear(), car.getYear());
+ assertEquals(Boolean.parseBoolean(vehicle.getPower()), car.isElectric());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "component", "converter" })
+ void mapEmployeeToEmployeeDtoFail(String value) {
+ Employee employee = new Employee();
+ employee.setId(1);
+ employee.setName("Mr Camel Quarkus");
+
+ // Mapping should fail because the configured mapper packages do not handle employee types
+ RestAssured.given()
+ .queryParam("fromTypeName", Employee.class.getName())
+ .queryParam("toTypeName", EmployeeDto.class.getName())
+ .body(employee.toString())
+ .post("/mapstruct/" + value)
+ .then()
+ .statusCode(500)
+ .body(containsString("NoTypeConversionAvailableException"));
+ }
+
+ @Test
+ void mapStructComponentPackages() {
+ RestAssured.get("/mapstruct/component/packages")
+ .then()
+ .statusCode(200)
+ .body(containsString(CarMapper.class.getPackageName()), containsString(VehicleMapper.class.getPackageName()));
+ }
+}
diff --git a/integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructRoutes.java b/integration-tests/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructExplicitPackagesTestProfile.java
similarity index 56%
rename from integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructRoutes.java
rename to integration-tests/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructExplicitPackagesTestProfile.java
index 3671184da9a..02c708ecb1c 100644
--- a/integration-tests-jvm/mapstruct/src/main/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructRoutes.java
+++ b/integration-tests/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructExplicitPackagesTestProfile.java
@@ -16,22 +16,17 @@
*/
package org.apache.camel.quarkus.component.mapstruct.it;
-import jakarta.inject.Named;
-import org.apache.camel.builder.RouteBuilder;
-import org.apache.camel.component.mapstruct.MapstructComponent;
-import org.apache.camel.quarkus.component.mapstruct.it.model.Car;
+import java.util.Map;
-public class MapStructRoutes extends RouteBuilder {
- @Named("mapstruct")
- MapstructComponent mapstruct() {
- MapstructComponent mapstruct = new MapstructComponent();
- mapstruct.setMapperPackageName("org.apache.camel.quarkus.component.mapstruct.it.mapper");
- return mapstruct;
- }
+import io.quarkus.test.junit.QuarkusTestProfile;
+import org.apache.camel.quarkus.component.mapstruct.it.mapper.car.CarMapper;
+import org.apache.camel.quarkus.component.mapstruct.it.mapper.vehicle.VehicleMapper;
+public class MapStructExplicitPackagesTestProfile implements QuarkusTestProfile {
@Override
- public void configure() throws Exception {
- from("direct:component").to("mapstruct:" + Car.class.getName());
- from("direct:converter").convertBodyTo(Car.class);
+ public Map getConfigOverrides() {
+ // Join package names with extra whitespace to verify it is trimmed at build time
+ return Map.of("camel.component.mapstruct.mapper-package-name",
+ String.join(" , ", CarMapper.class.getPackageName(), VehicleMapper.class.getPackageName()));
}
}
diff --git a/integration-tests-jvm/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructIT.java b/integration-tests/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructIT.java
similarity index 100%
rename from integration-tests-jvm/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructIT.java
rename to integration-tests/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructIT.java
diff --git a/integration-tests/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructTest.java b/integration-tests/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructTest.java
new file mode 100644
index 00000000000..d8c534bbd2b
--- /dev/null
+++ b/integration-tests/mapstruct/src/test/java/org/apache/camel/quarkus/component/mapstruct/it/MapStructTest.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.quarkus.component.mapstruct.it;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+import org.apache.camel.quarkus.component.mapstruct.CamelQuarkusMapStructMapperFinder;
+import org.apache.camel.quarkus.component.mapstruct.it.mapper.car.CarMapper;
+import org.apache.camel.quarkus.component.mapstruct.it.mapper.cat.CatMapper;
+import org.apache.camel.quarkus.component.mapstruct.it.mapper.dog.DogMapper;
+import org.apache.camel.quarkus.component.mapstruct.it.mapper.employee.EmployeeMapper;
+import org.apache.camel.quarkus.component.mapstruct.it.mapper.vehicle.VehicleMapper;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Bike;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Car;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Cat;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Dog;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Employee;
+import org.apache.camel.quarkus.component.mapstruct.it.model.EmployeeDto;
+import org.apache.camel.quarkus.component.mapstruct.it.model.Vehicle;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@QuarkusTest
+public class MapStructTest {
+ @ParameterizedTest
+ @ValueSource(strings = { "component", "converter" })
+ void mapVehicleToCar(String value) {
+ Vehicle vehicle = new Vehicle("Volvo", "XC60", "true", 2021);
+
+ String response = RestAssured.given()
+ .queryParam("fromTypeName", Vehicle.class.getName())
+ .queryParam("toTypeName", Car.class.getName())
+ .body(vehicle.toString())
+ .post("/mapstruct/" + value)
+ .then()
+ .statusCode(200)
+ .extract()
+ .body()
+ .asString();
+
+ Car car = Car.fromString(response);
+
+ assertEquals(vehicle.getCompany(), car.getBrand());
+ assertEquals(vehicle.getName(), car.getModel());
+ assertEquals(vehicle.getYear(), car.getYear());
+ assertEquals(Boolean.parseBoolean(vehicle.getPower()), car.isElectric());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "component", "converter" })
+ void mapBikeToCar(String value) {
+ Bike bike = new Bike("Honda", "CBR65R", 2023, false);
+
+ String response = RestAssured.given()
+ .queryParam("fromTypeName", Bike.class.getName())
+ .queryParam("toTypeName", Car.class.getName())
+ .body(bike.toString())
+ .post("/mapstruct/" + value)
+ .then()
+ .statusCode(200)
+ .extract()
+ .body()
+ .asString();
+
+ Car car = Car.fromString(response);
+
+ assertEquals(bike.getMake(), car.getBrand());
+ assertEquals(bike.getModelNumber(), car.getModel());
+ assertEquals(bike.getYear(), car.getYear());
+ assertEquals(bike.isElectric(), car.isElectric());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "component", "converter" })
+ void mapDogToCatWithAlternatePackageAndClassName(String value) {
+ Dog dog = new Dog("1", "Snoopy", 8, "bark");
+
+ String response = RestAssured.given()
+ .queryParam("fromTypeName", Dog.class.getName())
+ .queryParam("toTypeName", Cat.class.getName())
+ .body(dog.toString())
+ .post("/mapstruct/" + value)
+ .then()
+ .statusCode(200)
+ .extract()
+ .body()
+ .asString();
+
+ Cat cat = Cat.fromString(response);
+
+ assertEquals(dog.getDogId(), cat.getCatId());
+ assertEquals(dog.getName(), cat.getName());
+ assertEquals(dog.getAge(), cat.getAge());
+ assertEquals("meow", cat.getSound());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "component", "converter" })
+ void mapCatToDogWithApplicationScopedMapperBean(String value) {
+ Cat cat = new Cat("1", "Garfield", 12, "meow");
+
+ String response = RestAssured.given()
+ .queryParam("fromTypeName", Cat.class.getName())
+ .queryParam("toTypeName", Dog.class.getName())
+ .body(cat.toString())
+ .post("/mapstruct/" + value)
+ .then()
+ .statusCode(200)
+ .extract()
+ .body()
+ .asString();
+
+ Dog dog = Dog.fromString(response);
+
+ assertEquals(cat.getCatId(), dog.getDogId());
+ assertEquals(cat.getName(), dog.getName());
+ assertEquals(cat.getAge(), dog.getAge());
+ assertEquals("bark", dog.getVocalization());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "component", "converter" })
+ void mapEmployeeToEmployeeDtoWithAlternateClassName(String value) {
+ Employee employee = new Employee();
+ employee.setId(1);
+ employee.setName("Mr Camel Quarkus");
+
+ String response = RestAssured.given()
+ .queryParam("fromTypeName", Employee.class.getName())
+ .queryParam("toTypeName", EmployeeDto.class.getName())
+ .body(employee.toString())
+ .post("/mapstruct/" + value)
+ .then()
+ .statusCode(200)
+ .extract()
+ .body()
+ .asString();
+
+ EmployeeDto dto = EmployeeDto.fromString(response);
+ assertEquals(employee.getId(), dto.getEmployeeId());
+ assertEquals(employee.getName(), dto.getEmployeeName());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "component", "converter" })
+ void mapEmployeeDtoToEmployeeWithInheritedMapperMethod(String value) {
+ EmployeeDto dto = new EmployeeDto();
+ dto.setEmployeeId(1);
+ dto.setEmployeeName("Mr Camel Quarkus");
+
+ String response = RestAssured.given()
+ .queryParam("fromTypeName", EmployeeDto.class.getName())
+ .queryParam("toTypeName", Employee.class.getName())
+ .body(dto.toString())
+ .post("/mapstruct/" + value)
+ .then()
+ .statusCode(200)
+ .extract()
+ .body()
+ .asString();
+
+ Employee employee = Employee.fromString(response);
+ assertEquals(dto.getEmployeeId(), employee.getId());
+ assertEquals(dto.getEmployeeName(), employee.getName());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "component", "converter" })
+ void mapCarToVehicleUsingStaticFieldMapper(String value) {
+ Car car = new Car("Volvo", "XC60", 2021, true);
+
+ String response = RestAssured.given()
+ .queryParam("fromTypeName", Car.class.getName())
+ .queryParam("toTypeName", Vehicle.class.getName())
+ .body(car.toString())
+ .post("/mapstruct/" + value)
+ .then()
+ .statusCode(200)
+ .extract()
+ .body()
+ .asString();
+
+ Vehicle vehicle = Vehicle.fromString(response);
+
+ assertEquals(car.getBrand(), vehicle.getCompany());
+ assertEquals(car.getModel(), vehicle.getName());
+ assertEquals(car.getYear(), vehicle.getYear());
+ assertEquals(car.isElectric(), Boolean.parseBoolean(vehicle.getPower()));
+ }
+
+ @Test
+ void mapStructMapperFinderImpl() {
+ RestAssured.get("/mapstruct/finder/mapper")
+ .then()
+ .statusCode(200)
+ .body(is(CamelQuarkusMapStructMapperFinder.class.getName()));
+ }
+
+ @Test
+ void mapStructComponentPackages() {
+ RestAssured.get("/mapstruct/component/packages")
+ .then()
+ .statusCode(200)
+ .body(
+ containsString(CarMapper.class.getPackageName()),
+ containsString(CatMapper.class.getPackageName()),
+ containsString(DogMapper.class.getPackageName()),
+ containsString(EmployeeMapper.class.getPackageName()),
+ containsString(VehicleMapper.class.getPackageName()));
+ }
+}
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 54339ea2ddd..1a3fdd4d03a 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -159,6 +159,7 @@
lumberjack
mail
management
+ mapstruct
master
master-file
master-openshift
diff --git a/tooling/scripts/test-categories.yaml b/tooling/scripts/test-categories.yaml
index 426c338a2cf..6dc3d2ed948 100644
--- a/tooling/scripts/test-categories.yaml
+++ b/tooling/scripts/test-categories.yaml
@@ -88,6 +88,7 @@ group-05:
- datasonnet
- hl7
- jaxb
+ - mapstruct
- ssh
- soap
- xmlsecurity