diff --git a/core/citrus-api/src/main/java/org/citrusframework/message/MessagePayloadUtils.java b/core/citrus-api/src/main/java/org/citrusframework/message/MessagePayloadUtils.java
index 09145650a7..4c2a8824c8 100644
--- a/core/citrus-api/src/main/java/org/citrusframework/message/MessagePayloadUtils.java
+++ b/core/citrus-api/src/main/java/org/citrusframework/message/MessagePayloadUtils.java
@@ -212,4 +212,38 @@ public static String prettyPrintJson(String payload) {
}
return sb.toString();
}
+
+ /**
+ * Normalizes the given text by replacing all whitespace characters (identified by {@link Character#isWhitespace) by a single space
+ * and replacing windows style line endings with unix style line endings.
+ */
+ public static String normalizeWhitespace(String text, boolean normalizeWhitespace, boolean normalizeLineEndingsToUnix) {
+ if (text == null || text.isEmpty()) {
+ return text;
+ }
+
+ if (normalizeWhitespace) {
+ StringBuilder result = new StringBuilder();
+ boolean lastWasSpace = true;
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ if (Character.isWhitespace(c)) {
+ if (!lastWasSpace) {
+ result.append(' ');
+ }
+ lastWasSpace = true;
+ } else {
+ result.append(c);
+ lastWasSpace = false;
+ }
+ }
+ return result.toString().trim();
+ }
+
+ if (normalizeLineEndingsToUnix) {
+ return text.replaceAll("\\r(\\n)?", "\n");
+ }
+
+ return text;
+ }
}
diff --git a/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApi.java b/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApi.java
new file mode 100644
index 0000000000..a3d6bfdad2
--- /dev/null
+++ b/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApi.java
@@ -0,0 +1,45 @@
+package org.citrusframework.testapi;
+
+import java.util.Map;
+
+/**
+ * Interface representing a generated API from an OpenAPI specification.
+ * Provides methods to retrieve metadata about the API such as title, version,
+ * prefix, and information extensions.
+ */
+public interface GeneratedApi {
+
+ /**
+ * Retrieves the title of the OpenAPI specification, as specified in the info section of the API.
+ *
+ * @return the title of the OpenAPI specification
+ */
+ String getApiTitle();
+
+ /**
+ * Retrieves the version of the OpenAPI specification, as specified in the info section of the API.
+ *
+ * @return the version of the OpenAPI specification
+ */
+ String getApiVersion();
+
+ /**
+ * Retrieves the prefix used for the API, as specified in the API generation configuration.
+ *
+ * @return the prefix used for the API
+ */
+ String getApiPrefix();
+
+ /**
+ * Retrieves the specification extensions of the OpenAPI defined in the "info" section.
+ *
+ * Specification extensions, also known as vendor extensions, are custom key-value pairs used to describe extra
+ * functionality not covered by the standard OpenAPI Specification. These properties start with "x-".
+ * This method collects only the extensions defined in the "info" section of the API.
+ *
+ *
+ * @return a map containing the specification extensions defined in the "info" section of the API,
+ * where keys are extension names and values are extension values
+ */
+ Map getApiInfoExtensions();
+}
\ No newline at end of file
diff --git a/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApiRequest.java b/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApiRequest.java
new file mode 100644
index 0000000000..5b519ac118
--- /dev/null
+++ b/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApiRequest.java
@@ -0,0 +1,29 @@
+package org.citrusframework.testapi;
+
+/**
+ * Interface representing a generated API request corresponding to an operation in an OpenAPI specification.
+ * Provides methods to retrieve metadata about the request such as operation name, HTTP method, and path.
+ */
+public interface GeneratedApiRequest {
+
+ /**
+ * Retrieves the name of the OpenAPI operation associated with the request.
+ *
+ * @return the name of the OpenAPI operation
+ */
+ String getOperationName();
+
+ /**
+ * Retrieves the HTTP method used for the request.
+ *
+ * @return the HTTP method used for the request (e.g., GET, POST)
+ */
+ String getMethod();
+
+ /**
+ * Retrieves the path used for the request.
+ *
+ * @return the path used for the request
+ */
+ String getPath();
+}
diff --git a/core/citrus-api/src/test/java/org/citrusframework/message/MessagePayloadUtilsTest.java b/core/citrus-api/src/test/java/org/citrusframework/message/MessagePayloadUtilsTest.java
index 369385adc4..d89f582038 100644
--- a/core/citrus-api/src/test/java/org/citrusframework/message/MessagePayloadUtilsTest.java
+++ b/core/citrus-api/src/test/java/org/citrusframework/message/MessagePayloadUtilsTest.java
@@ -16,56 +16,79 @@
package org.citrusframework.message;
+import static org.testng.Assert.assertEquals;
+
import org.testng.Assert;
+import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class MessagePayloadUtilsTest {
@Test
public void shouldPrettyPrintJson() {
- Assert.assertEquals(MessagePayloadUtils.prettyPrint(""), "");
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("{}"), "{}");
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("[]"), "[]");
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\"}"),
+ assertEquals(MessagePayloadUtils.prettyPrint(""), "");
+ assertEquals(MessagePayloadUtils.prettyPrint("{}"), "{}");
+ assertEquals(MessagePayloadUtils.prettyPrint("[]"), "[]");
+ assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\"}"),
String.format("{%n \"user\": \"citrus\"%n}"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"text\":\";,{}' '[]:>\"}"),
+ assertEquals(MessagePayloadUtils.prettyPrint("{\"text\":\";,{}' '[]:>\"}"),
String.format("{%n \"text\": \";,{}' '[]:>\"%n}"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%n { \"user\":%n%n \"citrus\" }")),
+ assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%n { \"user\":%n%n \"citrus\" }")),
String.format("{%n \"user\": \"citrus\"%n}"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32}"),
+ assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32}"),
String.format("{%n \"user\": \"citrus\",%n \"age\": 32%n}"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("[22, 32]"),
+ assertEquals(MessagePayloadUtils.prettyPrint("[22, 32]"),
String.format("[%n22,%n32%n]"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("[{\"user\":\"citrus\",\"age\": 32}]"),
+ assertEquals(MessagePayloadUtils.prettyPrint("[{\"user\":\"citrus\",\"age\": 32}]"),
String.format("[%n {%n \"user\": \"citrus\",%n \"age\": 32%n }%n]"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("[{\"user\":\"citrus\",\"age\": 32}, {\"user\":\"foo\",\"age\": 99}]"),
+ assertEquals(MessagePayloadUtils.prettyPrint("[{\"user\":\"citrus\",\"age\": 32}, {\"user\":\"foo\",\"age\": 99}]"),
String.format("[%n {%n \"user\": \"citrus\",%n \"age\": 32%n },%n {%n \"user\": \"foo\",%n \"age\": 99%n }%n]"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32,\"pet\":{\"name\": \"fluffy\", \"age\": 4}}"),
+ assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32,\"pet\":{\"name\": \"fluffy\", \"age\": 4}}"),
String.format("{%n \"user\": \"citrus\",%n \"age\": 32,%n \"pet\": {%n \"name\": \"fluffy\",%n \"age\": 4%n }%n}"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32,\"pets\":[\"fluffy\",\"hasso\"]}"),
+ assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32,\"pets\":[\"fluffy\",\"hasso\"]}"),
String.format("{%n \"user\": \"citrus\",%n \"age\": 32,%n \"pets\": [%n \"fluffy\",%n \"hasso\"%n ]%n}"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"pets\":[\"fluffy\",\"hasso\"],\"age\": 32}"),
+ assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"pets\":[\"fluffy\",\"hasso\"],\"age\": 32}"),
String.format("{%n \"user\": \"citrus\",%n \"pets\": [%n \"fluffy\",%n \"hasso\"%n ],%n \"age\": 32%n}"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"pets\":[{\"name\": \"fluffy\", \"age\": 4},{\"name\": \"hasso\", \"age\": 2}],\"age\": 32}"),
+ assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"pets\":[{\"name\": \"fluffy\", \"age\": 4},{\"name\": \"hasso\", \"age\": 2}],\"age\": 32}"),
String.format("{%n \"user\": \"citrus\",%n \"pets\": [%n {%n \"name\": \"fluffy\",%n \"age\": 4%n },%n {%n \"name\": \"hasso\",%n \"age\": 2%n }%n ],%n \"age\": 32%n}"));
}
@Test
public void shouldPrettyPrintXml() {
- Assert.assertEquals(MessagePayloadUtils.prettyPrint(""), "");
- Assert.assertEquals(MessagePayloadUtils.prettyPrint(""),
+ assertEquals(MessagePayloadUtils.prettyPrint(""), "");
+ assertEquals(MessagePayloadUtils.prettyPrint(""),
String.format("%n%n"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("Citrus rocks!"),
+ assertEquals(MessagePayloadUtils.prettyPrint("Citrus rocks!"),
String.format("%n Citrus rocks!%n%n"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint("Citrus rocks!"),
+ assertEquals(MessagePayloadUtils.prettyPrint("Citrus rocks!"),
String.format("%n%n Citrus rocks!%n%n"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%nCitrus rocks!%n%n")),
+ assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%nCitrus rocks!%n%n")),
String.format("%n Citrus rocks!%n%n"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n %nCitrus rocks!%n %n")),
+ assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n %nCitrus rocks!%n %n")),
String.format("%n Citrus rocks!%n%n"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%n ")),
+ assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%n ")),
String.format("%n %n %n %n%n"));
- Assert.assertEquals(MessagePayloadUtils.prettyPrint(String.format("")),
+ assertEquals(MessagePayloadUtils.prettyPrint(String.format("")),
String.format("%n %n %n %n%n"));
}
+
+ @DataProvider
+ public Object[][] normalizeWhitespaceProvider() {
+ return new Object[][] {
+ // Test data: payload, ignoreWhitespace, ignoreNewLineType, expected result
+ {"Hello \t\r\nWorld\r\n", true, true, "Hello World"},
+ {"Hello \t\r\nWorld\r\n", true, false, "Hello World"},
+ {"Hello \t\r\nWorld\r\n", false, true, "Hello \t\nWorld\n"},
+ {"Hello \t\r\nWorld\r\n", false, false, "Hello \t\r\nWorld\r\n"},
+ {"", true, true, ""},
+ {"", false, false, ""},
+ {null, true, true, null},
+ {null, false, false, null}
+ };
+ }
+
+ @Test(dataProvider = "normalizeWhitespaceProvider")
+ public void normalizeWhitespace(String text, boolean normalizeWhitespace, boolean normalizeLineEndingsToUnix, String expected) {
+ assertEquals(MessagePayloadUtils.normalizeWhitespace(text, normalizeWhitespace, normalizeLineEndingsToUnix), expected);
+ }
}
diff --git a/core/citrus-base/src/main/java/org/citrusframework/testapi/ApiActionBuilderCustomizerService.java b/core/citrus-base/src/main/java/org/citrusframework/testapi/ApiActionBuilderCustomizerService.java
new file mode 100644
index 0000000000..2b77d97f7b
--- /dev/null
+++ b/core/citrus-base/src/main/java/org/citrusframework/testapi/ApiActionBuilderCustomizerService.java
@@ -0,0 +1,13 @@
+package org.citrusframework.testapi;
+
+import org.citrusframework.TestAction;
+import org.citrusframework.actions.SendMessageAction.SendMessageActionBuilder;
+import org.citrusframework.context.TestContext;
+
+/**
+ * Implementors of this interface are used to customize the SendMessageActionBuilder with application specific information. E.g. cookies
+ * or transactionIds.
+ */
+public interface ApiActionBuilderCustomizerService {
+ > T build(GeneratedApi generatedApi, TestAction action, TestContext context, T builder);
+}
diff --git a/pom.xml b/pom.xml
index 4a57b84102..69fc6c9c40 100644
--- a/pom.xml
+++ b/pom.xml
@@ -175,6 +175,7 @@
1.6.133.9.03.13.1
+ 3.3.03.0.13.3.11.11.2
@@ -250,6 +251,7 @@
3.2.04.1.105.Final4.12.0
+ 7.5.04.7.642.7.33.0.4
@@ -522,6 +524,19 @@
awaitility${awaitility.version}
+
+
+ org.openapitools
+ openapi-generator
+ ${openapi-generator-maven-plugin}
+ provided
+
+
+
+ org.springframework
+ spring-test
+ ${spring.version}
+ org.testngtestng
@@ -944,6 +959,11 @@
jackson-databind${jackson.version}
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+ ${jackson.version}
+ com.fasterxml.jackson.modulejackson-module-jaxb-annotations
diff --git a/src/manual/connector-openapi.adoc b/src/manual/connector-openapi.adoc
index 810fda6a05..8bf9511afa 100644
--- a/src/manual/connector-openapi.adoc
+++ b/src/manual/connector-openapi.adoc
@@ -309,3 +309,401 @@ Also, the server will verify the HTTP request method, the Content-Type header as
The given HTTP status code defines the response that should be sent by the server.
The server will generate a proper response according to the OpenAPI specification.
This also includes a potential response message body (e.g. pet object).
+
+[[openapi-server]]
+=== OpenAPI Test API Generator
+
+For an even deeper integration with a given OpenAPI, Citrus offers the possibility to generate a dedicated Test API which provides test actions tailored to the specific operations of the OpenAPI under evaluation.
+These actions can be used in XML or Java DSL.
+This functionality is provided by the `Citrus OpenAPI Test API Generator` which leverages the link:https://github.com/swagger-api/swagger-codegen/tree/master[OpenAPI Code Generator] to generate code, but provides custom templates tailored for seamless integration within the Citrus framework.
+
+The generator provides the following features:
+
+* generation of a Test API
+** from OpenAPI Specification
+** [TODO #1163] from WSDL via an intermediate step that generates a "light" OpenApi specification from a WSDL
+* integration into Citrus XML test cases
+** integration into XML editors via generated XSD
+*** schema validation
+*** auto completion
+* integration into Citrus Java test cases via Java DSL [TODO #1161]
+
+The following directory structure/table specifies the files, which are generated by the generator.
+Note that the `Prefix` is a configuration parameter which should uniquely identify a generated API.
+It is specified in the build configuration for the Test API.
+```
+target/
+├───generated-test-resources/
+│ ├───META-INF/
+│ │ ├───spring.handlers
+│ │ └───spring.schemas
+│ └───schema/
+│ └───xsd/
+│ └───prefix-api.xsd
+└───generated-test-sources/
+ └───org/
+ └───citrusframework/
+ └───automation/
+ └───prefix/
+ ├───api/
+ │ └───MyApi.java
+ ├───citrus/
+ │ ├───extension/
+ │ │ └───PrefixNamespaceHandler.java
+ │ ├───PrefixAbstractTestRequest.java
+ │ └───PrefixBeanDefinitionParser.java
+ ├───model/
+ │ ├───MyReqTypeA.java
+ │ └───MyReqTypeB.java
+ └───spring/
+ └───PrefixBeanConfiguration.java
+```
+
+|===
+| File | Content
+
+| `spring.handlers` | Spring namespace handler configuration, that contains all NamespaceHandlers for all generated APIs.
+| `spring.schemas` | Spring schema definitions, with mappings of namespaces to schemas for all generated APIs.
+| `prefix-api.xsd` | XSD schema for the integration of the Test API into XML.
+| `PrefixNamespaceHandler.java` | A Spring class, that registers bean definition parsers for Test API XML elements.
+| `PrefixAbstractTestRequest.java` | Abstract superclass of all Test API actions.
+| `PrefixBeanDefinitionParser.java` | Spring bean definition parser, responsible for parsing Test API XML elements into test actions.
+| `MyReqTypeA.java, MyReqTypeB.java` | Model files generated with respect to the schema definition of the OpenAPI.
+| `PrefixBeanConfiguration.java` | A Spring @Configuration class, that registers all Test API actions as Spring beans.
+|===
+
+==== Configuration of Test API generation
+
+Code generation is typically performed during the build process.
+For the Citrus Test API Generator, it is carried out by a Maven plugin.
+While the standard generator plugin, `org.openapitools:openapi-generator-maven-plugin`, can be employed for this purpose, configuring it can be cumbersome, especially when dealing with multiple APIs.
+To address this challenge, Citrus offers its adaptation of this standard generator Maven plugin.
+This `Citrus OpenAPI Generator Plugin` simplifies the configuration of test API generation by providing predefined defaults and supporting the generation of multiple APIs.
+Additionally, it enhances support for generating Spring integration files (`spring.handlers` and `spring.schemas`), thereby facilitating the integration of generated APIs into Spring-based applications.
+Consequently, utilizing the Citrus Generator Plugin is recommended in most scenarios.
+
+The following shows the configuration of test api generation for different scenarios:
+
+.Citrus OpenAPI Generator Plugin - multiple APIs, minimal configuration
+[source,xml,indent=0,role="primary"]
+----
+
+ citrus-test-api-generator-maven-plugin
+
+
+
+
+ Multi1
+
+
+
+ Multi2
+
+
+
+ Multi3
+
+
+
+
+
+
+
+ create-test-api
+
+
+
+
+
+----
+
+.Citrus OpenAPI Generator Plugin - single API full configuration
+[source,xml,indent=0,role="secondary"]
+----
+
+ citrus-test-api-generator-maven-plugin
+
+
+
+ my-generated-sources
+ my-generated-resources
+ myschema/xsd
+ src/main/resources/META-INF
+
+ Full
+
+ org.mypackage.%PREFIX%.api
+ myEndpoint
+ org.mypackage.%PREFIX%.invoker
+ org.mypackage.%PREFIX%.model
+ "http://company/citrus-test-api/myNamespace"
+
+
+
+
+
+
+
+ create-test-api
+
+
+
+
+----
+
+.Standard OpenAPI Generator Plugin
+[source,xml,indent=0,role="secondary"]
+----
+
+
+ org.openapitools
+ openapi-generator-maven-plugin
+
+
+
+ org.citrusframework
+ citrus-test-api-generator-core
+ ${project.version}
+
+
+
+
+ REST
+ generated-test-resources
+ generated-test-sources
+ true
+
+ true
+
+ java-citrus
+
+
+
+
+ generate-openapi-petstore-files
+ compile
+
+ generate
+
+
+ ${project.basedir}/src/test/resources/apis/petstore.yaml
+
+ org.citrusframework.openapi.generator.rest.petstore
+ org.citrusframework.openapi.generator.rest.petstore.request
+ org.citrusframework.openapi.generator.rest.petstore.model
+ PetStore
+ petStoreEndpoint
+
+
+
+
+ generate-openapi-files-for-soap
+ compile
+
+ generate
+
+
+ ${project.basedir}/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService-generated.yaml
+
+ SOAP
+ org.citrusframework.openapi.generator.soap.bookservice
+ org.citrusframework.openapi.generator.soap.bookservice.request
+ org.citrusframework.openapi.generator.soap.bookservice.model
+ SoapSample
+ OpenApiFromWsdl
+ soapSampleEndpoint
+
+
+
+
+
+----
+
+These are the primary elements you can configure in the `` section:
+
+|===
+| Configuration element | Maven Property | Description | Default Value
+
+| `schemaFolder` | `citrus.test.api.generator.schema.folder` | Location for the generated XSD schemas | `schema/xsd/%VERSION%`
+| `resourceFolder` | `citrus.test.api.generator.resource.folder` | Location to which resources are generated | `generated-resources`
+| `sourceFolder` | `citrus.test.api.generator.source.folder` | Location to which sources are generated | `generated-sources`
+| `metaInfFolder` | `citrus.test.api.generator.meta.inf.folder` | Location to which spring meta files are generated/updated | `target/generated-test-resources/META-INF`
+| `generateSpringIntegrationFiles` | `citrus.test.api.generator.generate.spring.integration.files` | Specifies whether spring integration files should be generated | `true`
+| Nested api element | | |
+| `prefix` | `citrus.test.api.generator.prefix` | Specifies the prefix used for the test API, typically an acronym | (no default, required)
+| `source` | `citrus.test.api.generator.source` | Specifies the source of the test API | (no default, required)
+| `version` | `citrus.test.api.generator.version` | Specifies the version of the API, may be null | (none)
+| `endpoint` | `citrus.test.api.generator.endpoint` | Specifies the endpoint of the test API | `applicationServiceClient`
+| `type` | `citrus.test.api.generator.type` | Specifies the type of the test API | `REST`, other option is `SOAP`
+| `useTags` | `citrus.test.api.generator.use.tags` | Specifies whether tags should be used by the generator | `true`
+| `invokerPackage` | `citrus.test.api.generator.invoker.package` | Package for the test API classes | `org.citrusframework.automation.%PREFIX%.%VERSION%`
+| `apiPackage` | `citrus.test.api.generator.api.package` | Package for the test API interface classes | `org.citrusframework.automation.%PREFIX%.%VERSION%.api`
+| `modelPackage` | `citrus.test.api.generator.model.package` | Package for the test API model classes | `org.citrusframework.automation.%PREFIX%.%VERSION%.model`
+| `targetXmlnsNamespace` | `citrus.test.api.generator.namespace` | XML namespace used by the API | `http://www.citrusframework.org/schema/%VERSION%/%PREFIX%-api`
+|===
+
+
+Note: `%PREFIX%` and `%VERSION%` are placeholders that will be replaced by their specific values as configured.
+The plugin performs a conversion to lowercase for `PREFIX` used in package names and in `targetXmlnsNamespace`.
+
+==== Running the generator
+
+To run the generator, execute the following command in your project directory:
+
+[source,bash]
+----
+mvn citrus-test-api-generator-maven-plugin:create-test-api
+----
+
+
+This command will generate the classes and XSD files as configured for your APIs in the specified locations.
+
+==== Spring meta file generation
+
+The `citrus-test-api-generator-maven-plugin` supports the generation of Spring integration files, specifically `spring.handlers` and `spring.schemas`.
+These files are essential for Spring applications utilizing XML configuration, as they provide mapping information for custom XML namespaces.
+
+===== Purpose
+
+The generated Spring integration files serve the purpose of mapping custom XML namespaces to their corresponding namespace handler and schema locations.
+This mapping allows Spring to properly parse and validate XML configuration files containing custom elements and attributes.
+
+===== Configuration
+
+The maven plugin generates these Spring integration files based on the provided configuration in the `citrus-test-api-generator-maven-plugin` section of the pom.xml file.
+For each API specified, the plugin writes entries into the `spring.handlers` and `spring.schemas` files according to the configured XML namespaces and their corresponding handlers and schemas.
+
+===== Important Consideration
+
+When there are other non-generated Spring schemas or handlers present in the `META-INF` folder, it's crucial to ensure that the `metaInfFolder` configuration points to the existing `META-INF` directory in the main resources, which is usually `src/main/resources/META-INF`.
+This ensures that the plugin correctly updates the existing files without overwriting them.
+
+To identify generated schemas, their namespace should include the following segment `citrus-test-schema`.
+During updates of the meta files, the generator filters out lines containing this segment from existing files and then re-adds them, preserving any non-generated content.
+
+==== Usage
+
+Once generated, the `spring.handlers` and `spring.schemas` files, along with any existing non-generated content, should be included in the resources of your Spring application.
+During runtime, Spring will use these files to resolve custom XML namespaces and handle elements accordingly.
+This automatically happens if one of the following folders is chosen:
+
+- `target/generated-test-resources/META-INF` (default)
+- `target/generated-resources/META-INF` for pure testing projects that provide their code on main rather than test
+- `src/main/resources/META-INF` - for mixing existing meta files with generated
+
+==== Configuration of the Test Classpath
+
+In case you choose to generate the API into `generated-test` folders, the maven build requires further configuration to add the `generated-test` folders to the classpath.
+The link:https://www.mojohaus.org/build-helper-maven-plugin/usage.html[build-helper-maven-plugin] is used to accomplish this configuration step.
+
+[source,xml]
+----
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-test-sources
+ generate-test-sources
+
+ add-test-source
+
+
+
+
+
+
+
+
+ add-test-resource
+ generate-test-resources
+
+ add-test-resource
+
+
+
+
+ ${project.build.directory}/generated-test-resources
+
+
+
+
+
+
+
+
+----
+
+==== Sample usage
+
+To utilize the test API in XML, it's necessary to import the respective namespace. Once imported, requests can be directly employed as actions, as illustrated in the sample below.
+Further examples can be found here `org.citrusframework.openapi.generator.GeneratedApiIT`.
+
+.XML DSL
+[source,xml,indent=0,role="secondary"]
+----
+
+
+
+
+
+
+
+
+
+
+
+
+----
+
+To utilize the test API in Java, it's necessary to import the API configuration, that provides the respective request actions.
+The request to test can then be autowired, configured and autowired, as illustrated in the sample below.
+Further examples can be found here `org.citrusframework.openapi.generator.GetPetByIdTest`.
+
+.Java DSL
+[source,java,indent=0,role="secondary"]
+----
+@ExtendWith(CitrusSpringExtension.class)
+@SpringBootTest(classes = {PetStoreBeanConfiguration.class, CitrusSpringConfig.class})
+class GetPetByIdTest {
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Autowired
+ private GetPetByIdRequest getPetByIdRequest;
+
+ @Test
+ @CitrusTest
+ void testByJsonPath(@CitrusResource TestCaseRunner runner) {
+
+ // Given
+ getPetByIdRequest.setPetId("1234");
+
+ // Then
+ getPetByIdRequest.setResponseStatus(HttpStatus.OK.value());
+ getPetByIdRequest.setResponseReasonPhrase(HttpStatus.OK.getReasonPhrase());
+
+ // Assert body by json path
+ getPetByIdRequest.setResponseValue(Map.of("$.name", "Snoopy"));
+
+ // When
+ runner.$(getPetByIdRequest);
+ }
+}
+
+----
diff --git a/src/manual/index.adoc b/src/manual/index.adoc
index 3e66b30e80..93f9f637e6 100644
--- a/src/manual/index.adoc
+++ b/src/manual/index.adoc
@@ -61,6 +61,8 @@ include::endpoint-restdocs.adoc[]
include::endpoint-component.adoc[]
include::endpoint-adapter.adoc[]
+include::testapi.adoc[]
+
include::connectors.adoc[]
include::connector-openapi.adoc[]
include::connector-jbang.adoc[]
diff --git a/test-api-generator/citrus-test-api-generator-core/pom.xml b/test-api-generator/citrus-test-api-generator-core/pom.xml
new file mode 100644
index 0000000000..c74f91249a
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/pom.xml
@@ -0,0 +1,202 @@
+
+ 4.0.0
+
+ citrus-test-api-generator
+ org.citrusframework
+ 4.3.0-SNAPSHOT
+ ../pom.xml
+
+
+ citrus-test-api-generator-core
+ jar
+
+ Citrus :: Test API Generator :: Core
+ Generates a Citrus Test-API for OpenAPI and WSDL specifications.
+
+
+
+
+ org.citrusframework
+ citrus-api
+ ${project.version}
+
+
+ org.citrusframework
+ citrus-http
+ ${project.version}
+
+
+ org.citrusframework
+ citrus-spring
+ ${project.version}
+ test
+
+
+ org.citrusframework
+ citrus-ws
+ ${project.version}
+
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+
+
+ org.openapitools
+ openapi-generator
+
+
+ wsdl4j
+ wsdl4j
+
+
+
+
+ org.citrusframework
+ citrus-junit5
+ ${project.version}
+ test
+
+
+ org.springframework.boot
+ spring-boot-test
+ ${spring.boot.test.version}
+ test
+
+
+ org.citrusframework
+ citrus-validation-json
+ ${project.version}
+ test
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ ${maven.helper.plugin.version}
+
+
+ add-generated-specs
+ generate-test-resources
+
+ add-test-resource
+
+
+
+
+ ${project.build.directory}/generated-test-resources
+ ${project.build.outputDirectory}
+
+
+
+
+
+ add-generated-classes
+ generate-test-sources
+
+ add-test-source
+
+
+
+
+
+
+
+
+
+
+
+ org.openapitools
+ openapi-generator-maven-plugin
+
+
+ org.citrusframework
+ citrus-test-api-generator-core
+ ${project.version}
+
+
+
+
+ REST
+ generated-test-resources
+ generated-test-sources
+ true
+
+ true
+ java-citrus
+
+
+
+
+ generate-openapi-petstore-files
+ compile
+
+ generate
+
+
+ ${project.basedir}/src/test/resources/apis/petstore.yaml
+
+ org.citrusframework.openapi.generator.rest.petstore
+ org.citrusframework.openapi.generator.rest.petstore.request
+ org.citrusframework.openapi.generator.rest.petstore.model
+ PetStore
+ petStoreEndpoint
+
+
+
+
+ generate-openapi-files-for-soap
+ compile
+
+ generate
+
+
+ ${project.basedir}/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService-generated.yaml
+
+ SOAP
+ org.citrusframework.openapi.generator.soap.bookservice
+ org.citrusframework.openapi.generator.soap.bookservice.request
+ org.citrusframework.openapi.generator.soap.bookservice.model
+ SoapSample
+ OpenApiFromWsdl
+ soapSampleEndpoint
+
+
+
+
+
+
+ generate-openapi-multiparttest-files
+ compile
+
+ generate
+
+
+ ${project.basedir}/src/test/resources/apis/multiparttest-rest-resource.yaml
+
+ org.citrusframework.openapi.generator.rest.multiparttest
+ org.citrusframework.openapi.generator.rest.multiparttest.request
+ org.citrusframework.openapi.generator.rest.multiparttest.model
+ MultipartTest
+ multipartTestEndpoint
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/JavaCitrusCodegen.java b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/JavaCitrusCodegen.java
new file mode 100644
index 0000000000..7aa18331b5
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/JavaCitrusCodegen.java
@@ -0,0 +1,345 @@
+package org.citrusframework.openapi.generator;
+
+import static java.util.Arrays.asList;
+import static java.util.stream.Collectors.toMap;
+import static org.openapitools.codegen.CliOption.newString;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import org.openapitools.codegen.CodegenType;
+import org.openapitools.codegen.SupportingFile;
+import org.openapitools.codegen.languages.AbstractJavaCodegen;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Thorsten Schlathoelter
+ */
+public class JavaCitrusCodegen extends AbstractJavaCodegen {
+
+ private static final Logger logger = LoggerFactory.getLogger(JavaCitrusCodegen.class);
+
+ private static final String ABSTRACT_TEST_REQUEST_JAVA = "AbstractTestRequest.java";
+ private static final String API_TYPE_REST = "REST";
+ private static final String API_TYPE_SOAP = "SOAP";
+ public static final String CODEGEN_NAME = "java-citrus";
+
+ // possible optional parameters
+ public static final String API_ENDPOINT = "apiEndpoint";
+ public static final String API_TYPE = "apiType";
+ public static final String GENERATED_SCHEMA_FOLDER = "generatedSchemaFolder";
+ public static final String HTTP_PATH_PREFIX = "httpPathPrefix";
+ public static final String OPENAPI_SCHEMA = "openapiSchema";
+ public static final String PREFIX = "prefix";
+ public static final String RESOURCE_FOLDER = "resourceFolder";
+ public static final String SOURCE_FOLDER = "sourceFolder";
+ public static final String TARGET_XMLNS_NAMESPACE = "targetXmlnsNamespace";
+
+ // default values for optional parameters
+ protected String apiPrefix = "Api";
+
+ protected String httpClient = API_ENDPOINT;
+ protected String httpPathPrefix = "api";
+ protected String openapiSchema = "oas3";
+ protected String resourceFolder =
+ "src" + File.separator + "main" + File.separator + "resources";
+ protected String generatedSchemaFolder = "schema" + File.separator + "xsd";
+ protected String targetXmlnsNamespace;
+
+ protected String apiVersion = "1.0.0";
+
+ public JavaCitrusCodegen() {
+ super();
+ // the root folder where all files are emitted
+ outputFolder = "generated-code" + File.separator + "java";
+
+ // this is the location which templates will be read from in the - resources - directory
+ templateDir = CODEGEN_NAME;
+
+ // register additional properties which will be available in the templates
+ additionalProperties.put("apiVersion", apiVersion);
+
+ // set default
+ additionalProperties.put(API_TYPE, API_TYPE_REST);
+
+ // add additional reserved words used in CitrusAbstractTestRequest and its base class to prevent name collisions
+ Set reservedWordsTemp = reservedWords();
+ reservedWordsTemp.addAll(
+ asList(
+ "name",
+ "description",
+ "actor",
+ "httpClient",
+ "dataSource",
+ "schemaValidation",
+ "schema",
+ "headerContentType",
+ "headerAccept",
+ "bodyFile",
+ "responseType",
+ "responseStatus",
+ "responseReasonPhrase",
+ "responseVersion",
+ "resource",
+ "responseVariable",
+ "responseValue",
+ "cookies",
+ "script",
+ "type"
+ )
+ );
+ setReservedWordsLowerCase(new ArrayList<>(reservedWordsTemp));
+
+ // add posibility to set a new value for the properties
+ cliOptions.add(newString(API_ENDPOINT,
+ "Which http client should be used (default " + httpClient + ")."));
+ cliOptions.add(
+ newString(
+ API_TYPE,
+ "Specifies the type of API to be generated specify SOAP to generate a SOAP API. By default a REST API will be generated"
+ )
+ );
+ cliOptions.add(
+ newString(GENERATED_SCHEMA_FOLDER,
+ "The schema output directory (default " + generatedSchemaFolder + ").")
+ );
+ cliOptions.add(newString(HTTP_PATH_PREFIX,
+ "Add a prefix to http path for all APIs (default " + httpPathPrefix + ")."));
+ cliOptions.add(newString(OPENAPI_SCHEMA,
+ "Which OpenAPI schema should be used (default " + openapiSchema + ")."));
+ cliOptions.add(
+ newString(
+ PREFIX,
+ "Add a prefix before the name of the files. First character should be upper case (default "
+ + apiPrefix + ")."
+ )
+ );
+ cliOptions.add(newString(PREFIX, "The api prefix (default " + apiPrefix + ")."));
+ cliOptions.add(newString(RESOURCE_FOLDER,
+ "Where the resource files are emitted (default " + resourceFolder + ")."));
+ cliOptions.add(
+ newString(TARGET_XMLNS_NAMESPACE,
+ "Xmlns namespace of the schema (default " + targetXmlnsNamespace + ").")
+ );
+ }
+
+ /**
+ * Returns human-friendly help for the generator. Provide the consumer with help tips,
+ * parameters here
+ *
+ * @return A string value for the help message
+ */
+ @Override
+ public String getHelp() {
+ return "Generates citrus api requests.";
+ }
+
+ /**
+ * Configures a friendly name for the generator. This will be used by the generator to select
+ * the library with the -g flag.
+ *
+ * @return the friendly name for the generator
+ */
+ @Override
+ public String getName() {
+ return CODEGEN_NAME;
+ }
+
+ /**
+ * Configures the type of generator.
+ *
+ * @return the CodegenType for this generator
+ * @see org.openapitools.codegen.CodegenType
+ */
+ @Override
+ public CodegenType getTag() {
+ return CodegenType.CLIENT;
+ }
+
+ @Override
+ public void processOpts() {
+ super.processOpts();
+
+ if (additionalProperties.containsKey(API_ENDPOINT)) {
+ this.setHttpClient(additionalProperties.get(API_ENDPOINT).toString());
+ }
+ additionalProperties.put(API_ENDPOINT, httpClient);
+
+ if (additionalProperties.containsKey(GENERATED_SCHEMA_FOLDER)) {
+ this.setGeneratedSchemaFolder(
+ additionalProperties.get(GENERATED_SCHEMA_FOLDER).toString());
+ }
+ additionalProperties.put(GENERATED_SCHEMA_FOLDER, generatedSchemaFolder);
+
+ if (additionalProperties.containsKey(HTTP_PATH_PREFIX)) {
+ this.setHttpPathPrefix(additionalProperties.get(HTTP_PATH_PREFIX).toString());
+ additionalProperties.put(HTTP_PATH_PREFIX, httpPathPrefix);
+ } else {
+ logger.warn(
+ "Using empty http-path-prefix for code generation. A http-path-prefix can be configured using \"{}\" property.",
+ HTTP_PATH_PREFIX
+ );
+ httpPathPrefix = "";
+ }
+
+ if (additionalProperties.containsKey(OPENAPI_SCHEMA)) {
+ this.setOpenapiSchema(additionalProperties.get(OPENAPI_SCHEMA).toString());
+ }
+ additionalProperties.put(OPENAPI_SCHEMA, openapiSchema);
+
+ if (additionalProperties.containsKey(PREFIX)) {
+ this.setApiPrefix(additionalProperties.get(PREFIX).toString());
+ additionalProperties.put(PREFIX, apiPrefix);
+ additionalProperties.put(PREFIX + "LowerCase", apiPrefix.toLowerCase());
+ } else {
+ logger.warn(
+ "Using empty prefix for code generation. A prefix can be configured using \"{}\" property.",
+ PREFIX);
+ apiPrefix = "";
+ }
+
+ if (additionalProperties.containsKey(RESOURCE_FOLDER)) {
+ this.setResourceFolder(additionalProperties.get(RESOURCE_FOLDER).toString());
+ }
+ additionalProperties.put(RESOURCE_FOLDER, resourceFolder);
+
+ if (additionalProperties.containsKey(TARGET_XMLNS_NAMESPACE)) {
+ this.setTargetXmlnsNamespace(
+ additionalProperties.get(TARGET_XMLNS_NAMESPACE).toString());
+ } else {
+ this.targetXmlnsNamespace = String.format(
+ "http://www.citrusframework.org/citrus-test-schema/%s-api", apiPrefix.toLowerCase());
+ }
+ additionalProperties.put(TARGET_XMLNS_NAMESPACE, targetXmlnsNamespace);
+
+ // define different folders where the files will be emitted
+ final String invokerFolder = (sourceFolder + File.separator + invokerPackage).replace(".",
+ File.separator);
+ final String citrusFolder = invokerFolder + File.separator + "citrus";
+ final String extensionFolder = citrusFolder + File.separator + "extension";
+ final String springFolder = invokerFolder + File.separator + "spring";
+ final String schemaFolder = resourceFolder + File.separator + generatedSchemaFolder;
+
+ Object apiType = additionalProperties.get(API_TYPE);
+ if (API_TYPE_REST.equals(apiType)) {
+ addRestSupportingFiles(citrusFolder, schemaFolder);
+ } else if (API_TYPE_SOAP.equals(apiType)) {
+ addSoapSupportingFiles(citrusFolder, schemaFolder);
+ } else {
+ throw new IllegalArgumentException(String.format("Unknown API_TYPE: '%s'", apiType));
+ }
+
+ addDefaultSupportingFiles(citrusFolder, extensionFolder, springFolder);
+ }
+
+ @Override
+ public void preprocessOpenAPI(OpenAPI openAPI) {
+ super.preprocessOpenAPI(openAPI);
+
+ Info info = openAPI.getInfo();
+ Map extensions = info.getExtensions();
+ if (extensions != null) {
+ additionalProperties.putAll(extensions);
+
+ Map infoExtensions = extensions.entrySet().stream()
+ .filter(entry -> entry.getKey().toUpperCase(
+ ).startsWith("X-"))
+ .collect(toMap(Entry::getKey, Entry::getValue));
+ additionalProperties.put("infoExtensions", infoExtensions);
+ }
+ }
+
+ public void setApiPrefix(String apiPrefix) {
+ this.apiPrefix = apiPrefix;
+ }
+
+ public String getHttpClient() {
+ return httpClient;
+ }
+
+ public void setHttpClient(String httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ public String getHttpPathPrefix() {
+ return httpPathPrefix;
+ }
+
+ public void setHttpPathPrefix(String httpPathPrefix) {
+ this.httpPathPrefix = httpPathPrefix;
+ }
+
+ public String getOpenapiSchema() {
+ return openapiSchema;
+ }
+
+ public void setOpenapiSchema(String openapiSchema) {
+ this.openapiSchema = openapiSchema;
+ }
+
+ public String getResourceFolder() {
+ return resourceFolder;
+ }
+
+ public void setResourceFolder(String resourceFolder) {
+ this.resourceFolder = resourceFolder;
+ }
+
+ public String getGeneratedSchemaFolder() {
+ return generatedSchemaFolder;
+ }
+
+ public void setGeneratedSchemaFolder(String generatedSchemaFolder) {
+ this.generatedSchemaFolder = generatedSchemaFolder;
+ }
+
+ public String getTargetXmlnsNamespace() {
+ return targetXmlnsNamespace;
+ }
+
+ public void setTargetXmlnsNamespace(String targetXmlnsNamespace) {
+ this.targetXmlnsNamespace = targetXmlnsNamespace;
+ }
+
+ public String getApiPrefix() {
+ return apiPrefix;
+ }
+
+ private void addRestSupportingFiles(final String citrusFolder, String schemaFolder) {
+ supportingFiles.add(new SupportingFile("schema.mustache", schemaFolder,
+ apiPrefix.toLowerCase() + "-api.xsd"));
+ supportingFiles.add(new SupportingFile("test_base.mustache", citrusFolder,
+ apiPrefix + ABSTRACT_TEST_REQUEST_JAVA));
+ }
+
+ private void addSoapSupportingFiles(final String citrusFolder, String schemaFolder) {
+ // Remove the default api template file
+ apiTemplateFiles().remove("api.mustache");
+ apiTemplateFiles().put("api_soap.mustache", ".java");
+
+ supportingFiles.add(new SupportingFile("schema_soap.mustache", schemaFolder,
+ apiPrefix.toLowerCase() + "-api.xsd"));
+ supportingFiles.add(new SupportingFile("api_soap.mustache", citrusFolder,
+ apiPrefix + ABSTRACT_TEST_REQUEST_JAVA));
+ supportingFiles.add(new SupportingFile("test_base_soap.mustache", citrusFolder,
+ apiPrefix + ABSTRACT_TEST_REQUEST_JAVA));
+ }
+
+ private void addDefaultSupportingFiles(final String citrusFolder, final String extensionFolder,
+ final String springFolder) {
+ supportingFiles.add(new SupportingFile("bean_configuration.mustache", springFolder,
+ apiPrefix + "BeanConfiguration.java"));
+ supportingFiles.add(new SupportingFile("bean_definition_parser.mustache", citrusFolder,
+ apiPrefix + "BeanDefinitionParser.java"));
+ supportingFiles.add(new SupportingFile("namespace_handler.mustache", extensionFolder,
+ apiPrefix + "NamespaceHandler.java"));
+ supportingFiles.add(new SupportingFile("api-model.mustache", resourceFolder,
+ apiPrefix.toLowerCase() + "-api-model.csv"));
+ }
+
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformer.java b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformer.java
new file mode 100644
index 0000000000..68d7bf3579
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformer.java
@@ -0,0 +1,187 @@
+package org.citrusframework.openapi.generator;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
+import static com.fasterxml.jackson.databind.MapperFeature.SORT_PROPERTIES_ALPHABETICALLY;
+import static java.lang.String.format;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.PathItem;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.responses.ApiResponses;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.wsdl.Binding;
+import javax.wsdl.BindingOperation;
+import javax.wsdl.Definition;
+import javax.wsdl.WSDLException;
+import javax.wsdl.factory.WSDLFactory;
+import javax.wsdl.xml.WSDLReader;
+import javax.xml.namespace.QName;
+import org.citrusframework.openapi.generator.exception.WsdlToOpenApiTransformationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Element;
+
+/**
+ * Transforms a wsdl specification into a simple OpenApi specification for usage with the OpenApiGenerator.
+ *
+ *
+ * Note that this transformer only transforms bindings from the wsdl into operations in the OpenApi.
+ */
+public class SimpleWsdlToOpenApiTransformer {
+
+ private static final Logger logger = LoggerFactory.getLogger(SimpleWsdlToOpenApiTransformer.class);
+
+ private static final YAMLMapper yamlMapper = (YAMLMapper) YAMLMapper.builder()
+ .enable(SORT_PROPERTIES_ALPHABETICALLY)
+ .build()
+ .setSerializationInclusion(NON_NULL);
+
+ private final URI wsdlUri;
+
+ public SimpleWsdlToOpenApiTransformer(URI wsdlUri) {
+ this.wsdlUri = wsdlUri;
+ }
+
+ /**
+ * Transforms the wsdl of this transfromer into a OpenApi yaml representation.
+ *
+ * @return the OpenApi yaml
+ * @throws WsdlToOpenApiTransformationException if the parsing fails
+ */
+ public String transformToOpenApi() throws WsdlToOpenApiTransformationException {
+ try {
+ Definition wsdlDefinition = readWSDL();
+ Map, ?> bindings = wsdlDefinition.getBindings();
+ OpenAPI openAPI = transformToOpenApi(bindings);
+ return convertToYaml(openAPI);
+ } catch (Exception e) {
+ throw new WsdlToOpenApiTransformationException("Unable to parse wsdl", e);
+ }
+ }
+
+ /**
+ * Performs the actual transformation from bindings into OpenApi operations.
+ *
+ * @param bindings
+ * @return
+ */
+ private OpenAPI transformToOpenApi(Map, ?> bindings) {
+ OpenAPI openAPI = new OpenAPI();
+ openAPI.setInfo(createInfo());
+
+ Paths paths = new Paths();
+ openAPI.setPaths(paths);
+ for (Entry, ?> entry : bindings.entrySet()) {
+ Object key = entry.getKey();
+ Object value = entry.getValue();
+
+ if (key instanceof QName && value instanceof Binding) {
+ addOperations(openAPI, (QName) key, (Binding) value);
+ }
+ }
+ return openAPI;
+ }
+
+ private Definition readWSDL() throws WSDLException {
+ logger.debug("Reading wsdl file from path: {}", wsdlUri);
+
+ WSDLReader reader = WSDLFactory.newInstance().newWSDLReader();
+
+ // switch off the verbose mode
+ reader.setFeature("javax.wsdl.verbose", false);
+ reader.setFeature("javax.wsdl.importDocuments", true);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Reading the WSDL. Base uri is {}", wsdlUri);
+ }
+
+ return reader.readWSDL(wsdlUri.toString());
+ }
+
+ private Info createInfo() {
+ Info info = new Info();
+ info.setTitle("Generated api from wsdl");
+
+ info.setDescription(
+ format(
+ "This api has been generated from the following wsdl '%s'. It's purpose is solely to serve as input for SOAP API generation. Note that only operations are extracted from the WSDL. No schema information whatsoever is generated!",
+ java.nio.file.Paths.get(wsdlUri).getFileName()
+ )
+ );
+ info.setVersion("1.0.0");
+
+ Contact contact = new Contact();
+ contact.setName("org.citrusframework.openapi.generator.SimpleWsdlToOpenApiTransformer");
+ info.setContact(contact);
+ return info;
+ }
+
+ private void addOperations(OpenAPI openApi, QName qName, Binding binding) {
+ String localPart = qName.getLocalPart();
+
+ String bindingApiName;
+ if (localPart.endsWith("SoapBinding")) {
+ bindingApiName = localPart.substring(0, localPart.length() - "SoapBinding".length());
+ } else {
+ bindingApiName = localPart;
+ }
+
+ List> bindingOperations = binding.getBindingOperations();
+ for (Object operation : bindingOperations) {
+ if (operation instanceof BindingOperation bindingOperation) {
+ addOperation(
+ openApi.getPaths(),
+ bindingOperation.getName(),
+ retrieveOperationDescription(bindingOperation),
+ bindingApiName
+ );
+ }
+ }
+ }
+
+ private void addOperation(Paths paths, String name, String description, String tag) {
+ Operation postOperation = new Operation();
+
+ logger.debug("Adding operation to spec: {}", name);
+
+ postOperation.setOperationId(name);
+ postOperation.setDescription(description);
+ postOperation.tags(Collections.singletonList(tag));
+ ApiResponses responses = new ApiResponses();
+ postOperation.responses(responses);
+
+ PathItem pi = new PathItem();
+ pi.setPost(postOperation);
+
+ paths.addPathItem("/" + name, pi);
+ }
+
+ /**
+ * Retrieve the description of the bindingOperation via the documentation of the associated operation.
+ */
+ private String retrieveOperationDescription(BindingOperation bindingOperation) {
+ String description = "";
+ javax.wsdl.Operation soapOperation = bindingOperation.getOperation();
+ if (soapOperation != null) {
+ Element documentationElement = soapOperation.getDocumentationElement();
+ if (documentationElement != null) {
+ description = documentationElement.getTextContent();
+ }
+ }
+
+ return description;
+ }
+
+ private String convertToYaml(OpenAPI openAPI) throws JsonProcessingException {
+ return yamlMapper.writeValueAsString(openAPI);
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/exception/WsdlToOpenApiTransformationException.java b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/exception/WsdlToOpenApiTransformationException.java
new file mode 100644
index 0000000000..fd845194bf
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/exception/WsdlToOpenApiTransformationException.java
@@ -0,0 +1,8 @@
+package org.citrusframework.openapi.generator.exception;
+
+public class WsdlToOpenApiTransformationException extends Exception {
+
+ public WsdlToOpenApiTransformationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/test-api-generator/citrus-test-api-generator-core/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
new file mode 100644
index 0000000000..33a85c5059
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
@@ -0,0 +1 @@
+org.citrusframework.openapi.generator.JavaCitrusCodegen
\ No newline at end of file
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api-model.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api-model.mustache
new file mode 100644
index 0000000000..ae513df561
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api-model.mustache
@@ -0,0 +1,2 @@
+OperationId;Path;Method;Parameters{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
+{{operationId}};{{path}};{{httpMethod}};{{#allParams}}{{paramName}}:{{{dataType}}}{{^-last}},{{/-last}}{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api.mustache
new file mode 100644
index 0000000000..5d90d5f80c
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api.mustache
@@ -0,0 +1,265 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package {{package}};
+
+import jakarta.annotation.Generated;
+import org.citrusframework.testapi.GeneratedApi;
+import org.citrusframework.testapi.GeneratedApiRequest;
+import jakarta.servlet.http.Cookie;
+import org.apache.commons.lang3.StringUtils;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.citrusframework.spi.Resources;
+import org.citrusframework.http.actions.HttpActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder.HttpMessageBuilderSupport;
+import org.citrusframework.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import {{invokerPackage}}.citrus.{{prefix}}AbstractTestRequest;
+
+import java.io.IOException;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class {{classname}} implements GeneratedApi
+{
+
+ public static final {{classname}} INSTANCE = new {{classname}}();
+
+ public String getApiTitle() {
+ return "{{appName}}";
+ }
+
+ public String getApiVersion() {
+ return "{{appVersion}}";
+ }
+
+ public String getApiPrefix() {
+ return "{{prefix}}";
+ }
+
+ public Map getApiInfoExtensions() {
+ Map infoExtensionMap = new HashMap<>();
+ {{#infoExtensions}}
+ {{#entrySet}}
+ infoExtensionMap.put("{{key}}", "{{value}}");
+ {{/entrySet}}
+ {{/infoExtensions}}
+ return infoExtensionMap;
+ }
+
+ {{#operations}}
+ {{#operation}}
+ /** {{operationId}} ({{httpMethod}} {{httpPathPrefix}}{{{path}}})
+ {{summary}}
+ {{description}}
+ **/
+ public static class {{operationIdCamelCase}}Request extends {{prefix}}AbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "{{httpPathPrefix}}{{{path}}}";
+ private final Logger coverageLogger = LoggerFactory.getLogger({{operationIdCamelCase}}Request.class);
+
+ {{#queryParams}}
+ private String {{paramName}};
+
+ {{/queryParams}}
+ {{#pathParams}}
+ private String {{paramName}};
+
+ {{/pathParams}}
+ {{#isMultipart}}
+ {{#formParams}}
+ private String {{paramName}};
+
+ {{/formParams}}
+ {{/isMultipart}}
+ {{#authMethods}}{{#isBasic}}
+ @Value("${" + "{{apiEndpoint}}.basic.username:#{null}}")
+ private String basicUsername;
+ @Value("${" + "{{apiEndpoint}}.basic.password:#{null}}")
+ private String basicPassword;
+
+ {{/isBasic}}
+ {{/authMethods}}
+
+ public {{operationIdCamelCase}}Request() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("{{prefix}}".toLowerCase() + ":{{operationId}}RequestType");
+ }
+
+ public String getOperationName() {
+ return "{{operationId}}";
+ }
+
+ public String getMethod() {
+ return "{{httpMethod}}";
+ }
+
+ public String getPath() {
+ return "{{path}}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ {{#isMultipart}}
+ MultiValueMap multiValues = new LinkedMultiValueMap<>();
+ {{#formParams}}
+ {{#required}}
+ if(StringUtils.isBlank({{paramName}})) {
+ throw new CitrusRuntimeException(String.format("Required attribute '%s' is not specified", "{{paramName}}"));
+ }
+ {{/required}}
+ {{#isBinary}}
+ if (StringUtils.isNotBlank({{paramName}})) {
+ multiValues.add("{{paramName}}", new ClassPathResource({{paramName}}));
+ bodyLog += {{paramName}}.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
+ }
+ {{/isBinary}}
+ {{^isBinary}}
+ if (StringUtils.isNotBlank({{paramName}})) {
+ // first try to load from resource
+ ClassPathResource resource = null;
+ try {
+ resource = new ClassPathResource({{paramName}});
+ }
+ catch(Exception ignore) {
+ // Use plain text instead of resource
+ }
+
+ if(resource != null && resource.exists()){
+ multiValues.add("{{paramName}}", resource);
+ } else {
+ multiValues.add("{{paramName}}", {{paramName}});
+ }
+ bodyLog += {{paramName}}.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
+ }
+ {{/isBinary}}
+ {{/formParams}}
+
+ bodyLog += "\";\"" + MediaType.MULTIPART_FORM_DATA_VALUE + "\"";
+ messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
+ .body(multiValues);
+
+ {{/isMultipart}}
+ {{^isMultipart}}
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+ {{/isMultipart}}
+
+ Map queryParams = new HashMap<>();
+ {{#allParams}}{{#isQueryParam}}
+
+ if (StringUtils.isNotBlank(this.{{paramName}})) {
+ queryParams.put("{{baseName}}", context.replaceDynamicContentInString(this.{{paramName}}));
+ httpClientRequestActionBuilder.queryParam("{{baseName}}", this.{{paramName}});
+ }
+ {{/isQueryParam}}{{/allParams}}
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+ {{#authMethods}}{{#isBasic}}
+
+ if(basicUsername != null && basicPassword != null){
+ messageBuilderSupport.header("Authorization", "Basic " + Base64.getEncoder().encodeToString((context.replaceDynamicContentInString(basicUsername)+":"+context.replaceDynamicContentInString(basicPassword)).getBytes()));
+ }
+ {{/isBasic}}{{/authMethods}}
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "{{operationId}};{{#lambda.uppercase}}{{httpMethod}}{{/lambda.uppercase}};\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+ {{#queryParams}}
+
+ public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) {
+ this.{{paramName}} = {{paramName}};
+ }
+ {{/queryParams}}
+ {{#pathParams}}
+
+ public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) {
+ this.{{paramName}} = {{paramName}};
+ }
+ {{/pathParams}}
+ {{#isMultipart}}
+ {{#formParams}}
+
+ public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) {
+ this.{{paramName}} = {{paramName}};
+ }
+ {{/formParams}}
+ {{/isMultipart}}
+ {{#authMethods}}{{#isBasic}}
+
+ public void setBasicUsername(String basicUsername) {
+ this.basicUsername = basicUsername;
+ }
+
+ public void setBasicPassword(String basicPassword) {
+ this.basicPassword = basicPassword;
+ }
+ {{/isBasic}}{{/authMethods}}
+ private String replacePathParams(String endpoint) {
+ {{#pathParams}}endpoint = endpoint.replace("{" + "{{baseName}}" + "}", {{paramName}});{{/pathParams}}
+ return endpoint;
+ }
+ }
+ {{/operation}}
+ {{/operations}}
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_doc.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_doc.mustache
new file mode 100644
index 0000000000..f8737ed4d9
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_doc.mustache
@@ -0,0 +1 @@
+# not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_soap.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_soap.mustache
new file mode 100644
index 0000000000..d39d1aff12
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_soap.mustache
@@ -0,0 +1,165 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package {{package}};
+
+import jakarta.annotation.Generated;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.citrusframework.testapi.GeneratedApi;
+import org.citrusframework.testapi.GeneratedApiRequest;
+import org.citrusframework.openapi.generator.soap.bookservice.citrus.OpenApiFromWsdlAbstractTestRequest;
+import org.citrusframework.spi.Resources;
+import org.citrusframework.util.FileUtils;
+import org.citrusframework.ws.actions.SendSoapMessageAction;
+import org.citrusframework.ws.actions.SendSoapMessageAction.Builder.SendSoapMessageBuilderSupport;
+import org.citrusframework.ws.actions.SoapActionBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.CollectionUtils;
+
+import {{invokerPackage}}.citrus.{{prefix}}AbstractTestRequest;
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class {{classname}} implements GeneratedApi
+{
+ public static final {{classname}} INSTANCE = new {{classname}}();
+
+ public String getApiTitle() {
+ return "{{appName}}";
+ }
+
+ public String getApiVersion() {
+ return "{{appVersion}}";
+ }
+
+ public String getApiPrefix() {
+ return "{{prefix}}";
+ }
+
+ public Map getApiInfoExtensions() {
+ Map infoExtensionMap = new HashMap<>();
+ {{#infoExtensions}}
+ {{#entrySet}}
+ infoExtensionMap.put("{{key}}", "{{value}}");
+ {{/entrySet}}
+ {{/infoExtensions}}
+ return infoExtensionMap;
+ }
+
+ {{#operations}}
+ {{#operation}}
+ /**
+ {{operationId}} ({{httpMethod}} {{httpPathPrefix}}{{{path}}})
+ {{summary}}
+ {{description}}
+ **/
+ public static class {{operationIdCamelCase}}Request extends {{prefix}}AbstractTestRequest implements GeneratedApiRequest {
+
+ private final Logger coverageLogger = LoggerFactory.getLogger({{operationIdCamelCase}}Request.class);
+
+ // Query params
+ {{#allParams}}{{#isQueryParam}}private String {{paramName}};
+ {{/isQueryParam}}{{/allParams}}
+
+ public {{operationIdCamelCase}}Request(){
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("{{prefix}}".toLowerCase() + ":{{operationId}}RequestType");
+ }
+
+ public String getOperationName() {
+ return "{{operationId}}";
+ }
+
+ public String getMethod() {
+ return "{{httpMethod}}";
+ }
+
+ public String getPath() {
+ return "{{path}}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+
+ SendSoapMessageAction.Builder soapSendMessageActionBuilder = new SoapActionBuilder().client(wsClient).send();
+ SendSoapMessageBuilderSupport messageBuilderSupport = soapSendMessageActionBuilder.getMessageBuilderSupport();
+
+ messageBuilderSupport.soapAction("{{operationId}}");
+
+ String payload = null;
+ String payloadType = null;
+
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ if (!CollectionUtils.isEmpty(soapHeaders)) {
+ for (Entry entry : soapHeaders.entrySet()) {
+ messageBuilderSupport = messageBuilderSupport.header(entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ if (!CollectionUtils.isEmpty(mimeHeaders)) {
+ for (Entry entry : mimeHeaders.entrySet()) {
+ messageBuilderSupport = messageBuilderSupport.header("citrus_http_" + entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ Map queryParams = new HashMap<>();
+ {{#allParams}}{{#isQueryParam}}
+ if (StringUtils.isNotBlank(this.{{paramName}})) {
+ queryParams.put("{{baseName}}", context.replaceDynamicContentInString(this.{{paramName}}));
+ sendSoapMessageActionBuilder.queryParam("{{baseName}}", this.{{paramName}});
+ }
+ {{/isQueryParam}}{{/allParams}}
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ soapSendMessageActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ soapSendMessageActionBuilder = customizeBuilder(INSTANCE, context, soapSendMessageActionBuilder);
+
+ soapSendMessageActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "{{operationId}};{{#lambda.uppercase}}{{httpMethod}}{{/lambda.uppercase}};\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"");
+ }
+
+ {{#allParams}}{{#isQueryParam}}
+ public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) {
+ this.{{paramName}} = {{paramName}};
+ }
+ {{/isQueryParam}}{{/allParams}}
+ }
+ {{/operation}}
+ {{/operations}}
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_test.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_test.mustache
new file mode 100644
index 0000000000..d5341fea2c
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_test.mustache
@@ -0,0 +1 @@
+// not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_configuration.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_configuration.mustache
new file mode 100644
index 0000000000..d4b8dd2e01
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_configuration.mustache
@@ -0,0 +1,38 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package {{invokerPackage}}.spring;
+
+import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
+
+{{#apiInfo}}
+{{#apis}}
+import {{package}}.{{classname}};
+{{/apis}}
+{{/apiInfo}}
+import javax.annotation.processing.Generated;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Scope;
+
+@Configuration
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class {{prefix}}BeanConfiguration {
+{{#apiInfo}}
+ {{#apis}}
+ {{#operations}}
+ {{#operation}}
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public {{classname}}.{{operationIdCamelCase}}Request {{operationId}}Request() {
+ return new {{classname}}.{{operationIdCamelCase}}Request();
+ }
+ {{/operation}}
+ {{/operations}}
+ {{/apis}}
+{{/apiInfo}}
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_definition_parser.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_definition_parser.mustache
new file mode 100644
index 0000000000..4e0957f1af
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_definition_parser.mustache
@@ -0,0 +1,215 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package {{invokerPackage}}.citrus;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.processing.Generated;
+
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.core.Conventions;
+import org.springframework.util.Assert;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.xml.DomUtils;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class {{prefix}}BeanDefinitionParser implements BeanDefinitionParser {
+
+ private static final String COOKIE = "cookie";
+ private static final String HEADER = "header";
+ private static final String SOAP_HEADER = "soapHeader";
+ private static final String MIME_HEADER = "mimeHeader";
+ private static final String NAME = "name";
+ private static final String REQUEST_BODY = "body";
+ private static final String REQUEST_BODY_LITERAL = "bodyLiteral";
+ private static final String MULTIPART_BODY = "multipartBody";
+ private static final String RESPONSE = "response";
+ private static final String RESPONSE_JSONPATH = "json-path";
+ private static final String RESPONSE_XPATH = "xpath";
+ private static final String EXPRESSION = "expression";
+ private static final String VALUE = "value";
+ private static final String RESPONSE_RESOURCE = "resource";
+ private static final String FILE = "file";
+ private static final String RESPONSE_VARIABLE = "responseVariable";
+ private static final String RESPONSE_VALUE = "responseValue";
+ private static final String SCRIPT = "script";
+ private static final String TYPE = "type";
+ private static final String SQL = "sql";
+ private static final String COLUMN = "column";
+ private static final String VARIABLE = "variable";
+ // new
+ private static final String SCHEMA = "schema";
+ // new
+ private static final String SCHEMA_VALIDATION = "schemaValidation";
+
+ private final Class> beanClass;
+
+ public {{prefix}}BeanDefinitionParser(Class> beanClass) {
+ this.beanClass = beanClass;
+ }
+
+ public BeanDefinition parse(Element element) {
+ return parse(element, null);
+ }
+
+ /**
+ * Note: The {@link {{prefix}}BeanDefinitionParser#parse(Element element)} allows access direct
+ * access without the {@link org.springframework.beans.factory.xml.ParserContext} for convenience.
+ */
+ @Override
+ public BeanDefinition parse(Element element, ParserContext parserContext) {
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClass);
+ retrieveRootNodeAttributes(element, builder);
+ retrieveOptionalNodeAttributes(element, REQUEST_BODY, builder);
+ retrieveTextContentAndNodeAttributes(element, REQUEST_BODY_LITERAL, builder);
+ retrieveOptionalNodeAttributes(element, RESPONSE, builder);
+ retrieveParamNodeData(element, builder, COOKIE);
+ retrieveParamNodeData(element, builder, HEADER);
+ retrieveParamNodeData(element, builder, SOAP_HEADER);
+ retrieveParamNodeData(element, builder, MIME_HEADER);
+ retrieveOptionalNodeAttributes(element, SCHEMA, builder);
+ retrieveOptionalNodeAttributes(element, SCHEMA_VALIDATION, builder);
+ retrieveOptionalMultipartElements(element, builder);
+ retrieveResponseNodeData(element, builder);
+ builder.addPropertyValue("name", element.getTagName());
+ return builder.getBeanDefinition();
+ }
+
+ private void retrieveOptionalMultipartElements(Element element, BeanDefinitionBuilder builder) {
+ var multipartBodyElement = DomUtils.getChildElementByTagName(element, MULTIPART_BODY);
+ if (multipartBodyElement != null) {
+ var multipartBodyChildElements = DomUtils.getChildElements(multipartBodyElement);
+ for(int i = 0; i < multipartBodyChildElements.size(); i++){
+ var multipartBodyChildElement = multipartBodyChildElements.get(i);
+ String propertyName = Conventions.attributeNameToPropertyName(multipartBodyChildElement.getLocalName());
+ builder.addPropertyValue(propertyName, multipartBodyChildElement.getTextContent());
+ }
+ }
+ }
+
+ private void retrieveRootNodeAttributes(Element element, BeanDefinitionBuilder builder) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty.");
+ builder.addPropertyValue(propertyName, attribute.getValue());
+ }
+ }
+
+ private void retrieveOptionalNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) {
+ if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) {
+ Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ NamedNodeMap attributes = el.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty.");
+ String variableName = el.getLocalName() + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
+ builder.addPropertyValue(variableName, attribute.getValue());
+ }
+ }
+ }
+
+ private void retrieveTextContentAndNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) {
+ if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) {
+ Element el1 = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ NamedNodeMap attributes = el1.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName1 = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName1), "Illegal property name returned, it must not be null or empty.");
+ String variableName = el1.getLocalName() + propertyName1.substring(0, 1).toUpperCase() + propertyName1.substring(1);
+ builder.addPropertyValue(variableName, attribute.getValue());
+ }
+ Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ builder.addPropertyValue(elementName, el.getTextContent());
+ }
+ }
+
+ private void retrieveParamNodeData(Element element, BeanDefinitionBuilder builder, String paramType) {
+ if (!DomUtils.getChildElementsByTagName(element, paramType).isEmpty()) {
+ Map params = new HashMap<>();
+ List elements = DomUtils.getChildElementsByTagName(element, paramType);
+ elements.forEach(e -> {
+ String name = e.getAttribute(NAME);
+ String value = e.getAttribute(VALUE);
+
+ Assert.state(StringUtils.isNotBlank(name), "Illegal attribute value returned. The 'name' attribute must not be null or empty.");
+ Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty.");
+
+ params.put(name, value);
+ });
+ builder.addPropertyValue(paramType, params);
+ }
+ }
+
+ private void retrieveResponseNodeData(Element element, BeanDefinitionBuilder builder) {
+
+ if (!DomUtils.getChildElementsByTagName(element, RESPONSE).isEmpty()) {
+ Element response = DomUtils.getChildElementsByTagName(element, RESPONSE).get(0);
+ List elements = DomUtils.getChildElements(response);
+
+ Map responseVariable = new HashMap<>();
+ Map responseValue = new HashMap<>();
+
+ for (int i = 0; i < elements.size(); i++) {
+ Element e = elements.get(i);
+
+ if (e.getTagName().contains(RESPONSE_JSONPATH) || e.getTagName().contains(RESPONSE_XPATH)) {
+ String expression = e.getAttribute(EXPRESSION);
+ String value = e.getAttribute(VALUE);
+
+ Assert.state(StringUtils.isNotBlank(expression), "Illegal attribute value returned. The 'expression' attribute must not be null or empty.");
+ Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty.");
+
+ // variable to save @variable('ebid')@ else value to validate
+ if (value.matches("\\@variable\\('.*'\\)\\@")) {
+ Matcher match = Pattern.compile("\\'(.*?)\\'").matcher(value);
+ if (match.find()) {
+ responseVariable.put(expression, value.substring(match.start() + 1, match.end() - 1));
+ }
+ } else {
+ responseValue.put(expression, value);
+ }
+ } else if (e.getTagName().contains(SCRIPT)) {
+ String script = e.getTextContent();
+ Assert.state(StringUtils.isNotBlank(script), "Illegal attribute value returned. The 'script' attribute must not be null or empty.");
+ builder.addPropertyValue(SCRIPT, script);
+
+ if (!e.getAttribute(TYPE).isEmpty()) {
+ String type = e.getAttribute(TYPE);
+ Assert.state(StringUtils.isNotBlank(type), "Illegal attribute value returned. The 'type' attribute must not be null or empty.");
+ builder.addPropertyValue(TYPE, type);
+ }
+ } else if (e.getTagName().contains(RESPONSE_RESOURCE)) {
+ String filePath = e.getAttribute(FILE);
+ Assert.state(StringUtils.isNotBlank(filePath), "Illegal attribute value returned. The 'file' attribute must not be null or empty.");
+ builder.addPropertyValue(RESPONSE_RESOURCE, filePath);
+ }
+
+ }
+
+ builder.addPropertyValue(RESPONSE_VARIABLE, responseVariable);
+ builder.addPropertyValue(RESPONSE_VALUE, responseValue);
+ }
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model.mustache
new file mode 100644
index 0000000000..d5341fea2c
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model.mustache
@@ -0,0 +1 @@
+// not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model_doc.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model_doc.mustache
new file mode 100644
index 0000000000..f8737ed4d9
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model_doc.mustache
@@ -0,0 +1 @@
+# not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/namespace_handler.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/namespace_handler.mustache
new file mode 100644
index 0000000000..ad260e35ba
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/namespace_handler.mustache
@@ -0,0 +1,36 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package {{invokerPackage}}.citrus.extension;
+
+{{#apiInfo}}
+{{#apis}}
+import {{package}}.{{classname}};
+{{/apis}}
+{{/apiInfo}}
+import {{invokerPackage}}.citrus.{{prefix}}BeanDefinitionParser;
+
+import javax.annotation.processing.Generated;
+
+import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class {{prefix}}NamespaceHandler extends NamespaceHandlerSupport {
+
+ @Override
+ public void init() {
+ {{#apiInfo}}
+ {{#apis}}
+ {{#operations}}
+ {{#operation}}
+ registerBeanDefinitionParser("{{operationId}}Request", new {{prefix}}BeanDefinitionParser({{classname}}.{{operationIdCamelCase}}Request.class));
+ {{/operation}}
+ {{/operations}}
+ {{/apis}}
+ {{/apiInfo}}
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/schema.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/schema.mustache
new file mode 100644
index 0000000000..faf2807dd1
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/schema.mustache
@@ -0,0 +1,217 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{#apiInfo}}
+ {{#apis}}
+ {{#operations}}
+ {{#operation}}
+
+ {{#isMultipart}}
+
+
+ {{#formParams}}
+
+
+
+ {{^required}}Optional {{/required}}{{#required}}Required{{/required}} - must either be set as attribute or element: {{#description}}
+
+ * If this test fails, it is essential to review the code generation process and underlying
+ * templates carefully. If the changes are intentional and verified, update the reference files by
+ * copying the generated API sources to the '/JavaCitrusCodegenIntegrationTest/expectedgen/'
+ * directory. To ensure accurate copying, without unwanted code formatting, use a simple File
+ * Explorer instead of relying on IDE-based operations.
+ */
+class JavaCitrusCodegenIT {
+
+ static Stream getResourcesForRest() throws IOException {
+ return geClassResourcesIgnoringInnerClasses("org/citrusframework/openapi/generator/rest");
+ }
+
+ @ParameterizedTest
+ @MethodSource("getResourcesForRest")
+ void testGeneratedFiles(Resource resource) throws IOException {
+ File classFile = resource.getFile();
+ String absolutePath = classFile.getAbsolutePath();
+ String javaFilePath = absolutePath.replace("test-classes", "generated-test-sources")
+ .replace(".class", ".java");
+
+ assertFileContent(new File(javaFilePath), "rest");
+ }
+
+ static Stream getResourcesForSoap() throws IOException {
+ return geClassResourcesIgnoringInnerClasses(
+ "org/citrusframework/openapi/generator/soap/bookservice");
+ }
+
+ @ParameterizedTest
+ @MethodSource("getResourcesForSoap")
+ void testGeneratedSoapFiles(Resource resource) throws IOException {
+ File classFile = resource.getFile();
+ String absolutePath = classFile.getAbsolutePath();
+
+ String javaFilePath = absolutePath.replace("test-classes", "generated-test-sources")
+ .replace(".class", ".java");
+
+ assertFileContent(new File(javaFilePath), "soap");
+ }
+
+ private static Stream geClassResourcesIgnoringInnerClasses(String path)
+ throws IOException {
+ return Streams.of(new PathMatchingResourcePatternResolver().getResources(
+ path + "/**/*.class")).filter(resource -> {
+ try {
+ return !resource.getURI().toString().contains("$");
+ } catch (Exception e) {
+ throw new CitrusRuntimeException("Unable to retrieve URL from resource!");
+ }
+ }).map(Arguments::arguments);
+ }
+
+ private void assertFileContent(File file, String apiDir) throws IOException {
+ assertThat(file).exists();
+ String expectedFilePath =
+ "org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/"
+ + file.getAbsolutePath().substring(file.getAbsolutePath().indexOf(apiDir));
+
+ ClassPathResource classPathResource = new ClassPathResource(expectedFilePath);
+
+ /*
+ * NOTE: when changes have been performed to mustache templates, the expected files need to be updated.
+ * Be aware that file content may change according to IDE formatting rules if the files are copied via IDE.
+ * Files should therefore be copied using a file explorer which ensures that content of files does not change.
+ */
+ assertThat(file).hasSameTextualContentAs(classPathResource.getFile());
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/JavaCitrusCodegenTest.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/JavaCitrusCodegenTest.java
new file mode 100644
index 0000000000..57f49cd861
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/JavaCitrusCodegenTest.java
@@ -0,0 +1,166 @@
+package org.citrusframework.openapi.generator;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.citrusframework.openapi.generator.JavaCitrusCodegen.CODEGEN_NAME;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.jupiter.api.Test;
+import org.openapitools.codegen.ClientOptInput;
+import org.openapitools.codegen.CodegenConfigLoader;
+import org.openapitools.codegen.DefaultGenerator;
+import org.openapitools.codegen.config.CodegenConfigurator;
+
+/**
+ * This test validates the code generation process.
+ *
+ * It may also serve as an entry point for debugging the code generation process. When executed in debug mode, it allows you to
+ * step through the generation process and inspect the resulting output in the specified output directory.
+ *
+ * To debug the code generator:
+ *
+ *
Set a breakpoint in the {@code postProcessOperationsWithModels()} method of {@code JavaCitrusCodegen.java}.
+ *
In your IDE, launch this test by right-clicking and selecting Debug As > JUnit Test.
+ *
+ */
+
+class JavaCitrusCodegenTest {
+
+ @Test
+ void retrieveGeneratorBsSpi() {
+ JavaCitrusCodegen codegen = (JavaCitrusCodegen) CodegenConfigLoader.forName("java-citrus");
+ assertThat(codegen).isNotNull();
+ }
+
+ @Test
+ void arePredefinedValuesNotEmptyTest() {
+ JavaCitrusCodegen codegen = new JavaCitrusCodegen();
+
+ assertThat(codegen.getName()).isEqualTo(CODEGEN_NAME);
+ assertThat(codegen.getHelp()).isNotEmpty();
+ assertThat(codegen.getHttpClient()).isNotEmpty();
+ assertThat(codegen.getOpenapiSchema()).isNotEmpty();
+ assertThat(codegen.getApiPrefix()).isNotEmpty();
+ assertThat(codegen.getHttpPathPrefix()).isNotEmpty();
+ assertThat(codegen.getTargetXmlnsNamespace()).isNull();
+ assertThat(codegen.getGeneratedSchemaFolder()).isNotEmpty();
+ }
+
+ @Test
+ void areAdditionalPropertiesProcessedTest() {
+ final String httpClient = "myTestEndpoint";
+ final String openapiSchema = "testSchema";
+ final String prefix = "testPrefix";
+ final String httpPathPrefix = "test/path";
+ final String targetXmlnsNamespace = "http://www.citrusframework.org/schema/test/extension";
+ final String generatedSchemaFolder = "generatedResourceFolder";
+
+ Map properties = new HashMap<>();
+ properties.put(JavaCitrusCodegen.API_ENDPOINT, httpClient);
+ properties.put(JavaCitrusCodegen.GENERATED_SCHEMA_FOLDER, generatedSchemaFolder);
+ properties.put(JavaCitrusCodegen.HTTP_PATH_PREFIX, httpPathPrefix);
+ properties.put(JavaCitrusCodegen.OPENAPI_SCHEMA, openapiSchema);
+ properties.put(JavaCitrusCodegen.PREFIX, prefix);
+ properties.put(JavaCitrusCodegen.TARGET_XMLNS_NAMESPACE, targetXmlnsNamespace);
+
+ JavaCitrusCodegen codegen = new JavaCitrusCodegen();
+ codegen.additionalProperties().putAll(properties);
+ codegen.processOpts();
+
+ assertThat(codegen.getApiPrefix()).isEqualTo(prefix);
+ assertThat(codegen.getGeneratedSchemaFolder()).isEqualTo(generatedSchemaFolder);
+ assertThat(codegen.getHttpClient()).isEqualTo(httpClient);
+ assertThat(codegen.getHttpPathPrefix()).isEqualTo(httpPathPrefix);
+ assertThat(codegen.getOpenapiSchema()).isEqualTo(openapiSchema);
+ assertThat(codegen.getTargetXmlnsNamespace()).isEqualTo(targetXmlnsNamespace);
+ }
+
+ @Test
+ void areReservedWordsEscapedTest() throws IOException {
+ final CodegenConfigurator configurator = new CodegenConfigurator()
+ .setGeneratorName(CODEGEN_NAME)
+ .setInputSpec("src/test/resources/apis/petstore_reservedWords.yaml")
+ .setOutputDir("target/JavaCitrusCodegenTest/petstore_escapedWords");
+
+ final ClientOptInput clientOptInput = configurator.toClientOptInput();
+ DefaultGenerator generator = new DefaultGenerator();
+ List outputFiles = generator.opts(clientOptInput).generate();
+
+ Optional file = outputFiles.stream().filter(x -> "PetApi.java".equals(x.getName()))
+ .findFirst();
+
+ assertThat(file).isPresent();
+
+ List lines = Files.readAllLines(file.get().toPath(), StandardCharsets.UTF_8);
+
+ // "name" is a reserved word, so it should be escaped with an underline for the second parameter
+ assertThat(lines.stream().filter(x -> x.contains("\"name\", this._name")).count()).isEqualTo(1L);
+ }
+
+ @Test
+ void arePathParamsFieldsPresent() throws IOException {
+ final CodegenConfigurator configurator = new CodegenConfigurator()
+ .setGeneratorName(CODEGEN_NAME)
+ .setInputSpec("src/test/resources/apis/petstore.yaml")
+ .setOutputDir("target/JavaCitrusCodegenTest/petstore");
+
+ final ClientOptInput clientOptInput = configurator.toClientOptInput();
+ DefaultGenerator generator = new DefaultGenerator();
+ List outputFiles = generator.opts(clientOptInput).generate();
+
+ Optional file = outputFiles.stream().filter(x -> "PetApi.java".equals(x.getName()))
+ .findFirst();
+
+ assertThat(file).isPresent();
+
+ List lines = Files.readAllLines(file.get().toPath(), StandardCharsets.UTF_8);
+
+ // "name" is a reserved word, so it should be escaped with an underline for the second parameter
+ assertThat(lines.stream().filter(x -> x.contains("private String petId;")).count()).isEqualTo(4L);
+ assertThat(lines.stream().filter(
+ x -> x.contains("endpoint = endpoint.replace(\"{\" + \"petId\" + \"}\", petId);"))
+ .count()).isEqualTo(4L);
+ }
+
+ @Test
+ void areBasicAuthFieldsPresent() throws IOException {
+ final CodegenConfigurator configurator = new CodegenConfigurator()
+ .setGeneratorName(CODEGEN_NAME)
+ .setInputSpec("src/test/resources/apis/petstore.yaml")
+ .setOutputDir("target/JavaCitrusCodegenTest/petstore");
+
+ final ClientOptInput clientOptInput = configurator.toClientOptInput();
+ DefaultGenerator generator = new DefaultGenerator();
+ List outputFiles = generator.opts(clientOptInput).generate();
+
+ Optional file = outputFiles.stream().filter(x -> "PetApi.java".equals(x.getName()))
+ .findFirst();
+
+ assertThat(file).isPresent();
+
+ List lines = Files.readAllLines(file.get().toPath(), StandardCharsets.UTF_8);
+
+ // "name" is a reserved word, so it should be escaped with an underline for the second parameter
+ assertThat(lines.stream()
+ .filter(x -> x.contains("@Value(\"${\" + \"apiEndpoint.basic.username:#{null}}\")"))
+ .count()).isEqualTo(1L);
+ assertThat(
+ lines.stream().filter(x -> x.contains("private String basicUsername;")).count()).isEqualTo(1L);
+ assertThat(
+ lines
+ .stream()
+ .filter(x ->
+ x.contains(
+ "messageBuilderSupport.header(\"Authorization\", \"Basic \" + Base64.getEncoder().encodeToString((context.replaceDynamicContentInString(basicUsername)+\":\"+context.replaceDynamicContentInString(basicPassword)).getBytes()));"
+ )
+ )
+ .count()
+ ).isEqualTo(1L);
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/ServiceLoaderTest.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/ServiceLoaderTest.java
new file mode 100644
index 0000000000..6b17f01f13
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/ServiceLoaderTest.java
@@ -0,0 +1,24 @@
+package org.citrusframework.openapi.generator;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.ServiceLoader.Provider;
+import org.citrusframework.testapi.ApiActionBuilderCustomizerService;
+import org.citrusframework.openapi.generator.util.TestApiActionBuilderCustomizer;
+import org.junit.jupiter.api.Test;
+
+class ServiceLoaderTest {
+
+ @Test
+ void test() {
+ ServiceLoader serviceLoader = ServiceLoader.load(
+ ApiActionBuilderCustomizerService.class, ApiActionBuilderCustomizerService.class.getClassLoader());
+ List> list = serviceLoader.stream().toList();
+ assertThat(list).hasSize(1);
+ ApiActionBuilderCustomizerService apiActionBuilderCustomizerService = list.iterator().next()
+ .get();
+ assertThat(apiActionBuilderCustomizerService).isInstanceOf(TestApiActionBuilderCustomizer.class);
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest.java
new file mode 100644
index 0000000000..1ae66986fd
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest.java
@@ -0,0 +1,29 @@
+package org.citrusframework.openapi.generator;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.citrusframework.util.FileUtils.readToString;
+
+import java.io.IOException;
+import org.citrusframework.openapi.generator.exception.WsdlToOpenApiTransformationException;
+import org.citrusframework.spi.Resource;
+import org.citrusframework.spi.Resources.ClasspathResource;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.io.ClassPathResource;
+
+class SimpleWsdlToOpenApiTransformerTest {
+
+ @Test
+ void testTransform() throws WsdlToOpenApiTransformationException, IOException {
+ ClassPathResource wsdlResource = new ClassPathResource(
+ "/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService.wsdl");
+
+ SimpleWsdlToOpenApiTransformer simpleWsdlToOpenApiTransformer = new SimpleWsdlToOpenApiTransformer(wsdlResource.getURI());
+ String generatedYaml = simpleWsdlToOpenApiTransformer.transformToOpenApi();
+
+ Resource expectedYamlResource = new ClasspathResource(
+ "/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService-generated.yaml");
+
+ String expectedYaml = readToString(expectedYamlResource);
+ assertThat(generatedYaml).isEqualToIgnoringWhitespace(expectedYaml);
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SpringBeanConfigurationIT.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SpringBeanConfigurationIT.java
new file mode 100644
index 0000000000..2f5dbf7179
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SpringBeanConfigurationIT.java
@@ -0,0 +1,55 @@
+package org.citrusframework.openapi.generator;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.citrusframework.annotations.CitrusResource;
+import org.citrusframework.annotations.CitrusTest;
+import org.citrusframework.config.CitrusSpringConfig;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.http.client.HttpClient;
+import org.citrusframework.http.client.HttpEndpointConfiguration;
+import org.citrusframework.junit.jupiter.spring.CitrusSpringSupport;
+import org.citrusframework.openapi.generator.rest.petstore.request.PetApi.AddPetRequest;
+import org.citrusframework.openapi.generator.rest.petstore.spring.PetStoreBeanConfiguration;
+import org.junit.jupiter.api.Test;
+import org.citrusframework.openapi.generator.SpringBeanConfigurationIT.ClientConfiguration;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.test.context.ContextConfiguration;
+
+@CitrusSpringSupport
+@ContextConfiguration(classes = {CitrusSpringConfig.class, ClientConfiguration.class, PetStoreBeanConfiguration.class})
+class SpringBeanConfigurationIT {
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Test
+ @CitrusTest
+ void fromReferenceResolverIsPrototypeScoped(@CitrusResource TestContext testContext) {
+ var addPetRequest = testContext.getReferenceResolver().resolve(AddPetRequest.class);
+ assertThat(addPetRequest)
+ .isNotNull()
+ .isNotEqualTo(testContext.getReferenceResolver().resolve(AddPetRequest.class));
+ }
+
+ @Test
+ void fromSpringApplicationContextIsPrototypeScoped() {
+ assertThat(applicationContext.getBean(AddPetRequest.class))
+ .isNotNull()
+ .isNotEqualTo(applicationContext.getBean(AddPetRequest.class));
+ }
+
+ @TestConfiguration
+ public static class ClientConfiguration {
+
+ @Bean(name= {"applicationServiceClient", "petStoreEndpoint"})
+ public HttpClient applicationServiceClient() {
+ var config = new HttpEndpointConfiguration();
+ config.setRequestUrl("http://localhost:9000");
+ return new HttpClient(config);
+ }
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/util/TestApiActionBuilderCustomizer.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/util/TestApiActionBuilderCustomizer.java
new file mode 100644
index 0000000000..0aaa9761ab
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/util/TestApiActionBuilderCustomizer.java
@@ -0,0 +1,22 @@
+package org.citrusframework.openapi.generator.util;
+
+import org.citrusframework.TestAction;
+import org.citrusframework.TestActionBuilder;
+import org.citrusframework.actions.SendMessageAction.SendMessageActionBuilder;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.testapi.ApiActionBuilderCustomizerService;
+import org.citrusframework.testapi.GeneratedApi;
+
+public class TestApiActionBuilderCustomizer implements ApiActionBuilderCustomizerService {
+
+ @Override
+ public > T build(GeneratedApi generatedApi, TestAction action,
+ TestContext context, T builder) {
+
+ generatedApi.getApiInfoExtensions().forEach((key, value) -> {
+ builder.getMessageBuilderSupport().header(key, value);
+ });
+
+ return builder;
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/services/org.citrusframework.testapi.ApiActionBuilderCustomizerService b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/services/org.citrusframework.testapi.ApiActionBuilderCustomizerService
new file mode 100644
index 0000000000..ba96f521f6
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/services/org.citrusframework.testapi.ApiActionBuilderCustomizerService
@@ -0,0 +1 @@
+org.citrusframework.openapi.generator.util.TestApiActionBuilderCustomizer
\ No newline at end of file
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.handlers b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.handlers
new file mode 100644
index 0000000000..1f0c4bdb95
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.handlers
@@ -0,0 +1,3 @@
+http\://www.citrusframework.org/citrus-test-schema/multiparttest-api=org.citrusframework.openapi.generator.rest.multiparttest.citrus.extension.MultipartTestNamespaceHandler
+http\://www.citrusframework.org/citrus-test-schema/openapifromwsdl-api=org.citrusframework.openapi.generator.soap.bookservice.citrus.extension.OpenApiFromWsdlNamespaceHandler
+http\://www.citrusframework.org/citrus-test-schema/petstore-api=org.citrusframework.openapi.generator.rest.petstore.citrus.extension.PetStoreNamespaceHandler
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.schemas b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.schemas
new file mode 100644
index 0000000000..0050010472
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.schemas
@@ -0,0 +1,3 @@
+http\://www.citrusframework.org/citrus-test-schema/multiparttest-api/multiparttest-api.xsd=schema/xsd/multiparttest-api.xsd
+http\://www.citrusframework.org/citrus-test-schema/openapifromwsdl-api/openapifromwsdl-api.xsd=schema/xsd/openapifromwsdl-api.xsd
+http\://www.citrusframework.org/citrus-test-schema/petstore-api/petstore-api.xsd=schema/xsd/petstore-api.xsd
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/multiparttest-rest-resource.yaml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/multiparttest-rest-resource.yaml
new file mode 100644
index 0000000000..b77b55c4d2
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/multiparttest-rest-resource.yaml
@@ -0,0 +1,249 @@
+openapi: 3.0.3
+info:
+ title: multiparttest API
+ version: 2.0.0
+ description: |
+ The service to test mutlipart
+ x-citrus-app: MPT
+ x-citrus-api-name: multiparttest-rest-resource
+ contact:
+ name: IT-Services-CI TAuBE
+ email: IT-Serv-CI-ETAdl@post.ch
+ url: https://confluence.pnet.ch/pages/viewpage.action?pageId=314828825
+tags:
+ - name: multiparttest-controller
+paths:
+ /api/v2/multitest-file/{bucket}/{filename}/random:
+ post:
+ tags:
+ - multiparttest-controller
+ operationId: postRandom
+ summary: Uploads random file.
+ parameters:
+ - name: bucket
+ description: The name of an existing s3 bucket.
+ in: path
+ required: true
+ schema:
+ type: string
+ - name: filename
+ description: The name under which to store the random file.
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PutObjectResult'
+ 500:
+ description: Internal Server Error
+ /api/v2/multitest-file/{bucket}/{filename}:
+ post:
+ tags:
+ - multiparttest-controller
+ operationId: postFile
+ summary: Uploads file.
+ parameters:
+ - name: bucket
+ description: The name of an existing s3 bucket.
+ in: path
+ required: true
+ schema:
+ type: string
+ - name: filename
+ description: The name of the file which should be uploaded. It may override any existing file with the same name.
+ in: path
+ required: true
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ properties:
+ multipartFile:
+ type: string
+ format: binary
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PutObjectResult'
+ 500:
+ description: Internal Server Error
+ delete:
+ tags:
+ - multiparttest-controller
+ operationId: deleteObject
+ summary: Delete file.
+ parameters:
+ - name: bucket
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The name of an existing s3 bucket.
+ - name: filename
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The name of the file which should be deleted.
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: boolean
+ 500:
+ description: Internal Server Error
+ /api/v2/multitest-reportgeneration:
+ post:
+ tags:
+ - multiparttest-controller
+ operationId: generateReport
+ summary: summary
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ required: ['template']
+ properties:
+ template:
+ description: |
+ Content of the template.
+ type: string
+ additionalData:
+ $ref: '#/components/schemas/AdditionalData'
+ schema:
+ description: |
+ An optional JSON schema to validate the created report against.
+ type: string
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PutObjectResult'
+ 500:
+ description: Internal Server Error
+ /api/v2/multitest-multipledatatypes:
+ post:
+ tags:
+ - multiparttest-controller
+ operationId: multipleDatatypes
+ summary: summary
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ properties:
+ stringData:
+ type: string
+ booleanData:
+ type: boolean
+ integerData:
+ type: integer
+
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PutObjectResult'
+ 500:
+ description: Internal Server Error
+ /api/v2/multitest-file/{bucket}/{filename}/exists:
+ get:
+ tags:
+ - multiparttest-controller
+ operationId: fileExists
+ summary: Checks if file exist.
+ parameters:
+ - name: bucket
+ description: The name of an existing s3 bucket.
+ in: path
+ required: true
+ schema:
+ type: string
+ - name: filename
+ description: The name of the file on which the status should be checked.
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: boolean
+ 500:
+ description: Internal Server Error
+components:
+ schemas:
+ Metadata:
+ type: object
+ properties:
+ userMetadata:
+ type: object
+ additionalProperties:
+ type: string
+ rawMetadata:
+ type: object
+ additionalProperties:
+ type: string
+ httpExpiresDate:
+ type: string
+ format: date-time
+ expirationTime:
+ type: string
+ format: date-time
+ expirationTimeRuleId:
+ type: string
+ ongoingRestore:
+ type: boolean
+ restoreExpirationTime:
+ type: string
+ format: date-time
+ bucketKeyEnabled:
+ type: boolean
+ PutObjectResult:
+ type: object
+ properties:
+ versionId:
+ type: string
+ eTag:
+ type: string
+ expirationTime:
+ type: string
+ format: date-time
+ expirationTimeRuleId:
+ type: string
+ contentMd5:
+ type: string
+ metadata:
+ $ref: '#/components/schemas/Metadata'
+ isRequesterCharged:
+ type: boolean
+ AdditionalData:
+ description: |
+ Additional data provided to the report. For each dataset requested, provide a json
+ object with the name of the dataset.
+ type: string
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore.yaml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore.yaml
new file mode 100644
index 0000000000..79249f26ed
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore.yaml
@@ -0,0 +1,700 @@
+swagger: '2.0'
+info:
+ description: 'This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.'
+ version: 1.0.0
+ x-citrus-app: PETS
+ x-citrus-api-name: petstore
+ title: OpenAPI Petstore
+ license:
+ name: Apache-2.0
+ url: 'https://www.apache.org/licenses/LICENSE-2.0.html'
+host: petstore.swagger.io
+basePath: /v2
+tags:
+ - name: pet
+ description: Everything about your Pets
+ - name: store
+ description: Access to Petstore orders
+ - name: user
+ description: Operations about user
+schemes:
+ - http
+paths:
+ /pet:
+ post:
+ tags:
+ - pet
+ summary: Add a new pet to the store
+ description: ''
+ operationId: addPet
+ consumes:
+ - application/json
+ - application/xml
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - in: body
+ name: body
+ description: Pet object that needs to be added to the store
+ required: true
+ schema:
+ $ref: '#/definitions/Pet'
+ responses:
+ '405':
+ description: Invalid input
+ security:
+ - petstore_auth:
+ - 'write:pets'
+ - 'read:pets'
+ put:
+ tags:
+ - pet
+ summary: Update an existing pet
+ description: ''
+ operationId: updatePet
+ consumes:
+ - application/json
+ - application/xml
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - in: body
+ name: body
+ description: Pet object that needs to be added to the store
+ required: true
+ schema:
+ $ref: '#/definitions/Pet'
+ responses:
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Pet not found
+ '405':
+ description: Validation exception
+ security:
+ - petstore_auth:
+ - 'write:pets'
+ - 'read:pets'
+ /pet/findByStatus:
+ get:
+ tags:
+ - pet
+ summary: Finds Pets by status
+ description: Multiple status values can be provided with comma separated strings
+ operationId: findPetsByStatus
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - name: status
+ in: query
+ description: Status values that need to be considered for filter
+ required: true
+ type: array
+ items:
+ type: string
+ enum:
+ - available
+ - pending
+ - sold
+ default: available
+ collectionFormat: csv
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ type: array
+ items:
+ $ref: '#/definitions/Pet'
+ '400':
+ description: Invalid status value
+ security:
+ - petstore_auth:
+ - 'write:pets'
+ - 'read:pets'
+ /pet/findByTags:
+ get:
+ tags:
+ - pet
+ summary: Finds Pets by tags
+ description: 'Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.'
+ operationId: findPetsByTags
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - name: tags
+ in: query
+ description: Tags to filter by
+ required: true
+ type: array
+ items:
+ type: string
+ collectionFormat: csv
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ type: array
+ items:
+ $ref: '#/definitions/Pet'
+ '400':
+ description: Invalid tag value
+ security:
+ - petstore_auth:
+ - 'write:pets'
+ - 'read:pets'
+ deprecated: true
+ '/pet/{petId}':
+ get:
+ tags:
+ - pet
+ summary: Find pet by ID
+ description: Returns a single pet
+ operationId: getPetById
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - name: petId
+ in: path
+ description: ID of pet to return
+ required: true
+ type: integer
+ format: int64
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ $ref: '#/definitions/Pet'
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Pet not found
+ security:
+ - api_key: []
+ - basicAuth: []
+ post:
+ tags:
+ - pet
+ summary: Updates a pet in the store with form data
+ description: ''
+ operationId: updatePetWithForm
+ consumes:
+ - application/x-www-form-urlencoded
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - name: petId
+ in: path
+ description: ID of pet that needs to be updated
+ required: true
+ type: integer
+ format: int64
+ - name: name
+ in: formData
+ description: Updated name of the pet
+ required: false
+ type: string
+ - name: status
+ in: formData
+ description: Updated status of the pet
+ required: false
+ type: string
+ responses:
+ '405':
+ description: Invalid input
+ security:
+ - petstore_auth:
+ - 'write:pets'
+ - 'read:pets'
+ delete:
+ tags:
+ - pet
+ summary: Deletes a pet
+ description: ''
+ operationId: deletePet
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - name: api_key
+ in: header
+ required: false
+ type: string
+ - name: petId
+ in: path
+ description: Pet id to delete
+ required: true
+ type: integer
+ format: int64
+ responses:
+ '400':
+ description: Invalid pet value
+ security:
+ - petstore_auth:
+ - 'write:pets'
+ - 'read:pets'
+ '/pet/{petId}/uploadImage':
+ post:
+ tags:
+ - pet
+ summary: uploads an image
+ description: ''
+ operationId: uploadFile
+ consumes:
+ - multipart/form-data
+ produces:
+ - application/json
+ parameters:
+ - name: petId
+ in: path
+ description: ID of pet to update
+ required: true
+ type: integer
+ format: int64
+ - name: additionalMetadata
+ in: formData
+ description: Additional data to pass to server
+ required: false
+ type: string
+ - name: file
+ in: formData
+ description: file to upload
+ required: false
+ type: file
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ $ref: '#/definitions/ApiResponse'
+ security:
+ - petstore_auth:
+ - 'write:pets'
+ - 'read:pets'
+ /store/inventory:
+ get:
+ tags:
+ - store
+ summary: Returns pet inventories by status
+ description: Returns a map of status codes to quantities
+ operationId: getInventory
+ produces:
+ - application/json
+ parameters: []
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ type: object
+ additionalProperties:
+ type: integer
+ format: int32
+ security:
+ - api_key: []
+ /store/order:
+ post:
+ tags:
+ - store
+ summary: Place an order for a pet
+ description: ''
+ operationId: placeOrder
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - in: body
+ name: body
+ description: order placed for purchasing the pet
+ required: true
+ schema:
+ $ref: '#/definitions/Order'
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ $ref: '#/definitions/Order'
+ '400':
+ description: Invalid Order
+ '/store/order/{order_id}':
+ get:
+ tags:
+ - store
+ summary: Find purchase order by ID
+ description: 'For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions'
+ operationId: getOrderById
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - name: order_id
+ in: path
+ description: ID of pet that needs to be fetched
+ required: true
+ type: integer
+ maximum: 5
+ minimum: 1
+ format: int64
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ $ref: '#/definitions/Order'
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Order not found
+ delete:
+ tags:
+ - store
+ summary: Delete purchase order by ID
+ description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
+ operationId: deleteOrder
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - name: order_id
+ in: path
+ description: ID of the order that needs to be deleted
+ required: true
+ type: string
+ responses:
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Order not found
+ /user:
+ post:
+ tags:
+ - user
+ summary: Create user
+ description: This can only be done by the logged in user.
+ operationId: createUser
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - in: body
+ name: body
+ description: Created user object
+ required: true
+ schema:
+ $ref: '#/definitions/User'
+ responses:
+ default:
+ description: successful operation
+ /user/createWithArray:
+ post:
+ tags:
+ - user
+ summary: Creates list of users with given input array
+ description: ''
+ operationId: createUsersWithArrayInput
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - in: body
+ name: body
+ description: List of user object
+ required: true
+ schema:
+ type: array
+ items:
+ $ref: '#/definitions/User'
+ responses:
+ default:
+ description: successful operation
+ /user/createWithList:
+ post:
+ tags:
+ - user
+ summary: Creates list of users with given input array
+ description: ''
+ operationId: createUsersWithListInput
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - in: body
+ name: body
+ description: List of user object
+ required: true
+ schema:
+ type: array
+ items:
+ $ref: '#/definitions/User'
+ responses:
+ default:
+ description: successful operation
+ /user/login:
+ get:
+ tags:
+ - user
+ summary: Logs user into the system
+ description: ''
+ operationId: loginUser
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - name: username
+ in: query
+ description: The user name for login
+ required: true
+ type: string
+ - name: password
+ in: query
+ description: The password for login in clear text
+ required: true
+ type: string
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ type: string
+ headers:
+ X-Rate-Limit:
+ type: integer
+ format: int32
+ description: calls per hour allowed by the user
+ X-Expires-After:
+ type: string
+ format: date-time
+ description: date in UTC when toekn expires
+ '400':
+ description: Invalid username/password supplied
+ /user/logout:
+ get:
+ tags:
+ - user
+ summary: Logs out current logged in user session
+ description: ''
+ operationId: logoutUser
+ produces:
+ - application/xml
+ - application/json
+ parameters: []
+ responses:
+ default:
+ description: successful operation
+ '/user/{username}':
+ get:
+ tags:
+ - user
+ summary: Get user by user name
+ description: ''
+ operationId: getUserByName
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - name: username
+ in: path
+ description: 'The name that needs to be fetched. Use user1 for testing.'
+ required: true
+ type: string
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ $ref: '#/definitions/User'
+ '400':
+ description: Invalid username supplied
+ '404':
+ description: User not found
+ put:
+ tags:
+ - user
+ summary: Updated user
+ description: This can only be done by the logged in user.
+ operationId: updateUser
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - name: username
+ in: path
+ description: name that need to be deleted
+ required: true
+ type: string
+ - in: body
+ name: body
+ description: Updated user object
+ required: true
+ schema:
+ $ref: '#/definitions/User'
+ responses:
+ '400':
+ description: Invalid user supplied
+ '404':
+ description: User not found
+ delete:
+ tags:
+ - user
+ summary: Delete user
+ description: This can only be done by the logged in user.
+ operationId: deleteUser
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ - name: username
+ in: path
+ description: The name that needs to be deleted
+ required: true
+ type: string
+ responses:
+ '400':
+ description: Invalid username supplied
+ '404':
+ description: User not found
+securityDefinitions:
+ petstore_auth:
+ type: oauth2
+ authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog'
+ flow: implicit
+ scopes:
+ 'write:pets': modify pets in your account
+ 'read:pets': read your pets
+ api_key:
+ type: apiKey
+ name: api_key
+ in: header
+ basicAuth:
+ type: basic
+definitions:
+ Order:
+ title: Pet Order
+ description: An order for a pets from the pet store
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ petId:
+ type: integer
+ format: int64
+ quantity:
+ type: integer
+ format: int32
+ shipDate:
+ type: string
+ format: date-time
+ status:
+ type: string
+ description: Order Status
+ enum:
+ - placed
+ - approved
+ - delivered
+ complete:
+ type: boolean
+ default: false
+ xml:
+ name: Order
+ Category:
+ title: Pet category
+ description: A category for a pet
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ name:
+ type: string
+ xml:
+ name: Category
+ User:
+ title: a User
+ description: A User who is purchasing from the pet store
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ username:
+ type: string
+ firstName:
+ type: string
+ lastName:
+ type: string
+ email:
+ type: string
+ password:
+ type: string
+ phone:
+ type: string
+ userStatus:
+ type: integer
+ format: int32
+ description: User Status
+ xml:
+ name: User
+ Tag:
+ title: Pet Tag
+ description: A tag for a pet
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ name:
+ type: string
+ xml:
+ name: Tag
+ Pet:
+ title: a Pet
+ description: A pet for sale in the pet store
+ type: object
+ required:
+ - name
+ - photoUrls
+ properties:
+ id:
+ type: integer
+ format: int64
+ category:
+ $ref: '#/definitions/Category'
+ name:
+ type: string
+ example: doggie
+ photoUrls:
+ type: array
+ xml:
+ name: photoUrl
+ wrapped: true
+ items:
+ type: string
+ tags:
+ type: array
+ xml:
+ name: tag
+ wrapped: true
+ items:
+ $ref: '#/definitions/Tag'
+ status:
+ type: string
+ description: pet status in the store
+ enum:
+ - available
+ - pending
+ - sold
+ xml:
+ name: Pet
+ ApiResponse:
+ title: An uploaded response
+ description: Describes the result of uploading an image resource
+ type: object
+ properties:
+ code:
+ type: integer
+ format: int32
+ type:
+ type: string
+ message:
+ type: string
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore_reservedWords.yaml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore_reservedWords.yaml
new file mode 100644
index 0000000000..7175b75f0e
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore_reservedWords.yaml
@@ -0,0 +1,120 @@
+swagger: '2.0'
+info:
+ description: 'This is a modified Petstore server, that uses the reserved word "name" as parameter name. This should be renamed to "_name" in the generated code.'
+ version: 1.0.0
+ title: OpenAPI Petstore
+ license:
+ name: Apache-2.0
+ url: 'https://www.apache.org/licenses/LICENSE-2.0.html'
+host: petstore.swagger.io
+basePath: /v2
+tags:
+ - name: pet
+ description: Everything about your Pets
+schemes:
+ - http
+paths:
+ /pet/findByName:
+ get:
+ tags:
+ - pet
+ summary: Finds Pet by name
+ description: Name can be any text
+ operationId: findPetByName
+ produces:
+ - application/xml
+ - application/json
+ parameters:
+ # name is a reserved word and should be masked with an '_' in the generated api
+ - name: name
+ in: query
+ description: Name of the pet
+ required: true
+ type: string
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ $ref: '#/definitions/Pet'
+ '400':
+ description: Invalid name value
+ security:
+ - petstore_auth:
+ - 'write:pets'
+ - 'read:pets'
+securityDefinitions:
+ petstore_auth:
+ type: oauth2
+ authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog'
+ flow: implicit
+ scopes:
+ 'write:pets': modify pets in your account
+ 'read:pets': read your pets
+ api_key:
+ type: apiKey
+ name: api_key
+ in: header
+definitions:
+ Category:
+ title: Pet category
+ description: A category for a pet
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ name:
+ type: string
+ xml:
+ name: Category
+ Tag:
+ title: Pet Tag
+ description: A tag for a pet
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ name:
+ type: string
+ xml:
+ name: Tag
+ Pet:
+ title: a Pet
+ description: A pet for sale in the pet store
+ type: object
+ required:
+ - name
+ - photoUrls
+ properties:
+ id:
+ type: integer
+ format: int64
+ category:
+ $ref: '#/definitions/Category'
+ name:
+ type: string
+ example: doggie
+ photoUrls:
+ type: array
+ xml:
+ name: photoUrl
+ wrapped: true
+ items:
+ type: string
+ tags:
+ type: array
+ xml:
+ name: tag
+ wrapped: true
+ items:
+ $ref: '#/definitions/Tag'
+ status:
+ type: string
+ description: pet status in the store
+ enum:
+ - available
+ - pending
+ - sold
+ xml:
+ name: Pet
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/citrus-context.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/citrus-context.xml
new file mode 100644
index 0000000000..3f2a783fca
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/citrus-context.xml
@@ -0,0 +1,7 @@
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/defaultOas3SchemaValidationTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/defaultOas3SchemaValidationTest.xml
new file mode 100644
index 0000000000..1784d89782
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/defaultOas3SchemaValidationTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnReasonPhraseTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnReasonPhraseTest.xml
new file mode 100644
index 0000000000..60f8dc25d0
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnReasonPhraseTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnStatusTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnStatusTest.xml
new file mode 100644
index 0000000000..ca47b78103
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnStatusTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnVersionTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnVersionTest.xml
new file mode 100644
index 0000000000..5047fc38a0
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnVersionTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/getPetByIdRequestTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/getPetByIdRequestTest.xml
new file mode 100644
index 0000000000..c6f1afc8b5
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/getPetByIdRequestTest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonDeactivatedSchemaValidationTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonDeactivatedSchemaValidationTest.xml
new file mode 100644
index 0000000000..7f4b46ed43
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonDeactivatedSchemaValidationTest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractTest.xml
new file mode 100644
index 0000000000..efb6b3e5a8
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractionTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractionTest.xml
new file mode 100644
index 0000000000..2ead5c459b
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractionTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationFailureTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationFailureTest.xml
new file mode 100644
index 0000000000..4d4bff102f
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationFailureTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationTest.xml
new file mode 100644
index 0000000000..320e32fcd9
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationFailureTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationFailureTest.xml
new file mode 100644
index 0000000000..3aeb456f25
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationFailureTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationTest.xml
new file mode 100644
index 0000000000..50f146bc18
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithFileAttributesTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithFileAttributesTest.xml
new file mode 100644
index 0000000000..4e24ad3e06
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithFileAttributesTest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithMultipleDatatypesTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithMultipleDatatypesTest.xml
new file mode 100644
index 0000000000..a727b3bd06
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithMultipleDatatypesTest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+ Test
+ true
+ 1
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithPlainTextTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithPlainTextTest.xml
new file mode 100644
index 0000000000..a3082ee857
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithPlainTextTest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+ ]]>
+
+
+ {"data1":"value1"}
+
+
+ {"schema":"mySchema"}
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/AdditionalData.json b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/AdditionalData.json
new file mode 100644
index 0000000000..a921a4b0c2
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/AdditionalData.json
@@ -0,0 +1,6 @@
+{
+ "Konto": {
+ "iban": "DE43100500000920018963",
+ "amount": 1234
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/MultipartTemplate.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/MultipartTemplate.xml
new file mode 100644
index 0000000000..a3dd52a043
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/MultipartTemplate.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/Schema.json b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/Schema.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/Schema.json
@@ -0,0 +1 @@
+{}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/addPetMessage.json b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/addPetMessage.json
new file mode 100644
index 0000000000..b68ed32a5e
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/addPetMessage.json
@@ -0,0 +1,6 @@
+{
+ "id": 12,
+ "name": "Snoopy",
+ "tags": ["comic dog"],
+ "photoUrls": ["url1", "url2"]
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json
new file mode 100644
index 0000000000..b68ed32a5e
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json
@@ -0,0 +1,6 @@
+{
+ "id": 12,
+ "name": "Snoopy",
+ "tags": ["comic dog"],
+ "photoUrls": ["url1", "url2"]
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage2.json b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage2.json
new file mode 100644
index 0000000000..267e7887a0
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage2.json
@@ -0,0 +1,6 @@
+{
+ "id": 12,
+ "name": "Garfield",
+ "tags": ["comic cat"],
+ "photoUrls": ["url1", "url2"]
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/postFileTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/postFileTest.xml
new file mode 100644
index 0000000000..a04b808966
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/postFileTest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/scriptValidationTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/scriptValidationTest.xml
new file mode 100644
index 0000000000..39efdef351
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/scriptValidationTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ assert json.id == 12
+ assert json.name == 'Snoopy'
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralTest.xml
new file mode 100644
index 0000000000..3daad673c3
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralTest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ {"id": 13}
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralWithVariableTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralWithVariableTest.xml
new file mode 100644
index 0000000000..11aacdf5a0
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralWithVariableTest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+ {"id": ${id}}
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyTest.xml
new file mode 100644
index 0000000000..8cb689bf3f
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyTest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithExtraHeaderTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithExtraHeaderTest.xml
new file mode 100644
index 0000000000..b2b002aa24
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithExtraHeaderTest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestAbstractTestRequest.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestAbstractTestRequest.java
new file mode 100644
index 0000000000..d35d8934bf
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestAbstractTestRequest.java
@@ -0,0 +1,245 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.multiparttest.citrus;
+
+
+import static org.springframework.util.CollectionUtils.isEmpty;
+
+import jakarta.annotation.Generated;
+import jakarta.annotation.Nullable;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import javax.sql.DataSource;
+import org.citrusframework.actions.AbstractTestAction;
+import org.citrusframework.actions.ReceiveMessageAction;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.http.actions.HttpActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder;
+import org.citrusframework.http.actions.HttpClientResponseActionBuilder;
+import org.citrusframework.http.actions.HttpClientResponseActionBuilder.HttpMessageBuilderSupport;
+import org.citrusframework.http.client.HttpClient;
+import org.citrusframework.message.Message;
+import org.citrusframework.testapi.ApiActionBuilderCustomizerService;
+import org.citrusframework.testapi.GeneratedApi;
+import org.citrusframework.spi.Resources;
+import org.citrusframework.validation.DelegatingPayloadVariableExtractor;
+import org.citrusframework.validation.PathExpressionValidationContext;
+import org.citrusframework.validation.json.JsonMessageValidationContext;
+import org.citrusframework.validation.script.ScriptValidationContext;
+import org.slf4j.Marker;
+import org.slf4j.MarkerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public abstract class MultipartTestAbstractTestRequest extends AbstractTestAction {
+
+ protected final Marker coverageMarker = MarkerFactory.getMarker("MULTIPARTTEST-API-COVERAGE");
+
+ @Autowired
+ @Qualifier("multipartTestEndpoint")
+ protected HttpClient httpClient;
+
+ @Autowired(required = false)
+ protected DataSource dataSource;
+
+ @Autowired(required = false)
+ private List actionBuilderCustomizerServices;
+
+ // attributes of differentNodes
+ protected boolean schemaValidation;
+ protected String schema;
+ protected String bodyContentType;
+ protected String bodyLiteralContentType;
+ protected String bodyFile;
+ protected String bodyLiteral;
+ protected String responseAcceptType = "*/*";
+ protected String responseType = "json";
+ protected int responseStatus = 200;
+ protected String responseReasonPhrase = "OK";
+ protected String responseVersion = "HTTP/1.1";
+
+ // children of response element
+ protected String resource;
+ protected Map responseVariable; // Contains the 'JSON-PATH' as key and the 'VARIABLE NAME' as value
+ protected Map responseValue; // Contains the 'JSON-PATH' as key and the 'VALUE TO BE VALIDATED' as value
+ protected Map cookies;
+ protected Map headers;
+ protected String script;
+ protected String type; // default script type is groovy - supported types see com.consol.citrus.script.ScriptTypes
+
+ @Override
+ public void doExecute(TestContext context) {
+ sendRequest(context);
+ recieveResponse(context);
+ }
+
+ /**
+ * This method receives the HTTP-Response.
+ *
+ * @deprecated use {@link MultipartTestAbstractTestRequest#receiveResponse(TestContext)} instead.
+ */
+ public ReceiveMessageAction recieveResponse(TestContext context) {
+
+ HttpClientResponseActionBuilder httpClientResponseActionBuilder = new HttpActionBuilder().client(httpClient).receive().response();
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientResponseActionBuilder.getMessageBuilderSupport();
+
+ messageBuilderSupport
+ .statusCode(responseStatus)
+ .reasonPhrase(responseReasonPhrase)
+ .version(responseVersion)
+ .validate(new JsonMessageValidationContext.Builder().schemaValidation(schemaValidation).schema(schema));
+
+ if (resource != null) {
+ messageBuilderSupport.body(Resources.create(resource));
+ }
+
+ if (!isEmpty(responseVariable)) {
+ DelegatingPayloadVariableExtractor.Builder extractorBuilder = new DelegatingPayloadVariableExtractor.Builder();
+ responseVariable.forEach(extractorBuilder::expression);
+ messageBuilderSupport.extract(extractorBuilder);
+ }
+
+ if (!isEmpty(responseValue)) {
+ PathExpressionValidationContext.Builder validationContextBuilder = new PathExpressionValidationContext.Builder();
+ responseValue.forEach(validationContextBuilder::expression);
+ messageBuilderSupport.validate(validationContextBuilder);
+ }
+
+ if (script != null) {
+ ScriptValidationContext.Builder scriptValidationContextBuilder = new ScriptValidationContext.Builder();
+ if (type != null) {
+ scriptValidationContextBuilder.scriptType(type);
+ }
+ scriptValidationContextBuilder.script(script);
+ messageBuilderSupport.validate(scriptValidationContextBuilder);
+ }
+
+ messageBuilderSupport.type(responseType);
+ httpClientResponseActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ var responseAction = httpClientResponseActionBuilder.build();
+
+ responseAction.execute(context);
+
+ return responseAction;
+ }
+
+ public @Nullable Message receiveResponse(TestContext context) {
+ var responseAction = recieveResponse(context);
+
+ var messageStore = context.getMessageStore();
+ return messageStore.getMessage(messageStore.constructMessageName(responseAction, httpClient));
+ }
+
+ public abstract void sendRequest(TestContext context);
+
+ public void setSchemaValidation(boolean schemaValidation) {
+ this.schemaValidation = schemaValidation;
+ }
+
+ public void setSchema(String schema) {
+ this.schema = schema;
+ }
+
+ public void setBodyLiteral(String bodyLiteral) {
+ this.bodyLiteral = bodyLiteral;
+ }
+
+ public void setBodyContentType(String bodyContentType) {
+ this.bodyContentType = bodyContentType;
+ }
+
+ public void setBodyLiteralContentType(String bodyLiteralContentType) {
+ this.bodyLiteralContentType = bodyLiteralContentType;
+ }
+
+ public void setResponseAcceptType(String responseAcceptType) {
+ this.responseAcceptType = responseAcceptType;
+ }
+
+ public void setCookie(Map cookies) {
+ this.cookies = cookies;
+ }
+
+ public void setHeader(Map headers) {
+ this.headers = headers;
+ }
+
+ public void setBodyFile(String bodyFile) {
+ this.bodyFile = bodyFile;
+ }
+
+ public void setResponseType(String responseType) {
+ this.responseType = responseType;
+ }
+
+ public void setResponseStatus(int responseStatus) {
+ this.responseStatus = responseStatus;
+ }
+
+ public void setResponseReasonPhrase(String responseReasonPhrase) {
+ this.responseReasonPhrase = responseReasonPhrase;
+ }
+
+ public void setResponseVersion(String responseVersion) {
+ this.responseVersion = responseVersion;
+ }
+
+ public void setResource(String resource) {
+ this.resource = resource;
+ }
+
+ public void setResponseVariable(Map responseVariable) {
+ this.responseVariable = responseVariable;
+ }
+
+ public void setResponseValue(Map responseValue) {
+ this.responseValue = responseValue;
+ }
+
+ public void setScript(String script) {
+ this.script = script;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ protected HttpClientRequestActionBuilder customizeBuilder(GeneratedApi generatedApi,
+ TestContext context, HttpClientRequestActionBuilder httpClientRequestActionBuilder) {
+
+ httpClientRequestActionBuilder = customizeByBeans(generatedApi, context,
+ httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder = customizeBySpi(generatedApi, context, httpClientRequestActionBuilder);
+
+ return httpClientRequestActionBuilder;
+ }
+
+ private HttpClientRequestActionBuilder customizeBySpi(GeneratedApi generatedApi, TestContext context,
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder) {
+ ServiceLoader serviceLoader = ServiceLoader.load(
+ ApiActionBuilderCustomizerService.class, ApiActionBuilderCustomizerService.class.getClassLoader());
+ for (ApiActionBuilderCustomizerService service :serviceLoader) {
+ httpClientRequestActionBuilder = service.build(generatedApi, this, context, httpClientRequestActionBuilder);
+ }
+ return httpClientRequestActionBuilder;
+ }
+
+ private HttpClientRequestActionBuilder customizeByBeans(
+ GeneratedApi generatedApi, TestContext context,
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder) {
+ if (actionBuilderCustomizerServices != null) {
+ for (ApiActionBuilderCustomizerService apiActionBuilderCustomizer : actionBuilderCustomizerServices) {
+ httpClientRequestActionBuilder = apiActionBuilderCustomizer.build(generatedApi, this,
+ context, httpClientRequestActionBuilder);
+ }
+ }
+ return httpClientRequestActionBuilder;
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestBeanDefinitionParser.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestBeanDefinitionParser.java
new file mode 100644
index 0000000000..d9730d0d42
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestBeanDefinitionParser.java
@@ -0,0 +1,215 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.multiparttest.citrus;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.processing.Generated;
+
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.core.Conventions;
+import org.springframework.util.Assert;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.xml.DomUtils;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class MultipartTestBeanDefinitionParser implements BeanDefinitionParser {
+
+ private static final String COOKIE = "cookie";
+ private static final String HEADER = "header";
+ private static final String SOAP_HEADER = "soapHeader";
+ private static final String MIME_HEADER = "mimeHeader";
+ private static final String NAME = "name";
+ private static final String REQUEST_BODY = "body";
+ private static final String REQUEST_BODY_LITERAL = "bodyLiteral";
+ private static final String MULTIPART_BODY = "multipartBody";
+ private static final String RESPONSE = "response";
+ private static final String RESPONSE_JSONPATH = "json-path";
+ private static final String RESPONSE_XPATH = "xpath";
+ private static final String EXPRESSION = "expression";
+ private static final String VALUE = "value";
+ private static final String RESPONSE_RESOURCE = "resource";
+ private static final String FILE = "file";
+ private static final String RESPONSE_VARIABLE = "responseVariable";
+ private static final String RESPONSE_VALUE = "responseValue";
+ private static final String SCRIPT = "script";
+ private static final String TYPE = "type";
+ private static final String SQL = "sql";
+ private static final String COLUMN = "column";
+ private static final String VARIABLE = "variable";
+ // new
+ private static final String SCHEMA = "schema";
+ // new
+ private static final String SCHEMA_VALIDATION = "schemaValidation";
+
+ private final Class> beanClass;
+
+ public MultipartTestBeanDefinitionParser(Class> beanClass) {
+ this.beanClass = beanClass;
+ }
+
+ public BeanDefinition parse(Element element) {
+ return parse(element, null);
+ }
+
+ /**
+ * Note: The {@link MultipartTestBeanDefinitionParser#parse(Element element)} allows access direct
+ * access without the {@link org.springframework.beans.factory.xml.ParserContext} for convenience.
+ */
+ @Override
+ public BeanDefinition parse(Element element, ParserContext parserContext) {
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClass);
+ retrieveRootNodeAttributes(element, builder);
+ retrieveOptionalNodeAttributes(element, REQUEST_BODY, builder);
+ retrieveTextContentAndNodeAttributes(element, REQUEST_BODY_LITERAL, builder);
+ retrieveOptionalNodeAttributes(element, RESPONSE, builder);
+ retrieveParamNodeData(element, builder, COOKIE);
+ retrieveParamNodeData(element, builder, HEADER);
+ retrieveParamNodeData(element, builder, SOAP_HEADER);
+ retrieveParamNodeData(element, builder, MIME_HEADER);
+ retrieveOptionalNodeAttributes(element, SCHEMA, builder);
+ retrieveOptionalNodeAttributes(element, SCHEMA_VALIDATION, builder);
+ retrieveOptionalMultipartElements(element, builder);
+ retrieveResponseNodeData(element, builder);
+ builder.addPropertyValue("name", element.getTagName());
+ return builder.getBeanDefinition();
+ }
+
+ private void retrieveOptionalMultipartElements(Element element, BeanDefinitionBuilder builder) {
+ var multipartBodyElement = DomUtils.getChildElementByTagName(element, MULTIPART_BODY);
+ if (multipartBodyElement != null) {
+ var multipartBodyChildElements = DomUtils.getChildElements(multipartBodyElement);
+ for(int i = 0; i < multipartBodyChildElements.size(); i++){
+ var multipartBodyChildElement = multipartBodyChildElements.get(i);
+ String propertyName = Conventions.attributeNameToPropertyName(multipartBodyChildElement.getLocalName());
+ builder.addPropertyValue(propertyName, multipartBodyChildElement.getTextContent());
+ }
+ }
+ }
+
+ private void retrieveRootNodeAttributes(Element element, BeanDefinitionBuilder builder) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty.");
+ builder.addPropertyValue(propertyName, attribute.getValue());
+ }
+ }
+
+ private void retrieveOptionalNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) {
+ if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) {
+ Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ NamedNodeMap attributes = el.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty.");
+ String variableName = el.getLocalName() + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
+ builder.addPropertyValue(variableName, attribute.getValue());
+ }
+ }
+ }
+
+ private void retrieveTextContentAndNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) {
+ if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) {
+ Element el1 = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ NamedNodeMap attributes = el1.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName1 = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName1), "Illegal property name returned, it must not be null or empty.");
+ String variableName = el1.getLocalName() + propertyName1.substring(0, 1).toUpperCase() + propertyName1.substring(1);
+ builder.addPropertyValue(variableName, attribute.getValue());
+ }
+ Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ builder.addPropertyValue(elementName, el.getTextContent());
+ }
+ }
+
+ private void retrieveParamNodeData(Element element, BeanDefinitionBuilder builder, String paramType) {
+ if (!DomUtils.getChildElementsByTagName(element, paramType).isEmpty()) {
+ Map params = new HashMap<>();
+ List elements = DomUtils.getChildElementsByTagName(element, paramType);
+ elements.forEach(e -> {
+ String name = e.getAttribute(NAME);
+ String value = e.getAttribute(VALUE);
+
+ Assert.state(StringUtils.isNotBlank(name), "Illegal attribute value returned. The 'name' attribute must not be null or empty.");
+ Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty.");
+
+ params.put(name, value);
+ });
+ builder.addPropertyValue(paramType, params);
+ }
+ }
+
+ private void retrieveResponseNodeData(Element element, BeanDefinitionBuilder builder) {
+
+ if (!DomUtils.getChildElementsByTagName(element, RESPONSE).isEmpty()) {
+ Element response = DomUtils.getChildElementsByTagName(element, RESPONSE).get(0);
+ List elements = DomUtils.getChildElements(response);
+
+ Map responseVariable = new HashMap<>();
+ Map responseValue = new HashMap<>();
+
+ for (int i = 0; i < elements.size(); i++) {
+ Element e = elements.get(i);
+
+ if (e.getTagName().contains(RESPONSE_JSONPATH) || e.getTagName().contains(RESPONSE_XPATH)) {
+ String expression = e.getAttribute(EXPRESSION);
+ String value = e.getAttribute(VALUE);
+
+ Assert.state(StringUtils.isNotBlank(expression), "Illegal attribute value returned. The 'expression' attribute must not be null or empty.");
+ Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty.");
+
+ // variable to save @variable('ebid')@ else value to validate
+ if (value.matches("\\@variable\\('.*'\\)\\@")) {
+ Matcher match = Pattern.compile("\\'(.*?)\\'").matcher(value);
+ if (match.find()) {
+ responseVariable.put(expression, value.substring(match.start() + 1, match.end() - 1));
+ }
+ } else {
+ responseValue.put(expression, value);
+ }
+ } else if (e.getTagName().contains(SCRIPT)) {
+ String script = e.getTextContent();
+ Assert.state(StringUtils.isNotBlank(script), "Illegal attribute value returned. The 'script' attribute must not be null or empty.");
+ builder.addPropertyValue(SCRIPT, script);
+
+ if (!e.getAttribute(TYPE).isEmpty()) {
+ String type = e.getAttribute(TYPE);
+ Assert.state(StringUtils.isNotBlank(type), "Illegal attribute value returned. The 'type' attribute must not be null or empty.");
+ builder.addPropertyValue(TYPE, type);
+ }
+ } else if (e.getTagName().contains(RESPONSE_RESOURCE)) {
+ String filePath = e.getAttribute(FILE);
+ Assert.state(StringUtils.isNotBlank(filePath), "Illegal attribute value returned. The 'file' attribute must not be null or empty.");
+ builder.addPropertyValue(RESPONSE_RESOURCE, filePath);
+ }
+
+ }
+
+ builder.addPropertyValue(RESPONSE_VARIABLE, responseVariable);
+ builder.addPropertyValue(RESPONSE_VALUE, responseValue);
+ }
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/extension/MultipartTestNamespaceHandler.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/extension/MultipartTestNamespaceHandler.java
new file mode 100644
index 0000000000..0d81b2d521
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/extension/MultipartTestNamespaceHandler.java
@@ -0,0 +1,29 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.multiparttest.citrus.extension;
+
+import org.citrusframework.openapi.generator.rest.multiparttest.request.MultiparttestControllerApi;
+import org.citrusframework.openapi.generator.rest.multiparttest.citrus.MultipartTestBeanDefinitionParser;
+
+import javax.annotation.processing.Generated;
+
+import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class MultipartTestNamespaceHandler extends NamespaceHandlerSupport {
+
+ @Override
+ public void init() {
+ registerBeanDefinitionParser("deleteObjectRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.DeleteObjectRequest.class));
+ registerBeanDefinitionParser("fileExistsRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.FileExistsRequest.class));
+ registerBeanDefinitionParser("generateReportRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.GenerateReportRequest.class));
+ registerBeanDefinitionParser("multipleDatatypesRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.MultipleDatatypesRequest.class));
+ registerBeanDefinitionParser("postFileRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.PostFileRequest.class));
+ registerBeanDefinitionParser("postRandomRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.PostRandomRequest.class));
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/Metadata.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/Metadata.java
new file mode 100644
index 0000000000..d5341fea2c
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/Metadata.java
@@ -0,0 +1 @@
+// not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/PutObjectResult.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/PutObjectResult.java
new file mode 100644
index 0000000000..d5341fea2c
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/PutObjectResult.java
@@ -0,0 +1 @@
+// not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/request/MultiparttestControllerApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/request/MultiparttestControllerApi.java
new file mode 100644
index 0000000000..36d2bca5f4
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/request/MultiparttestControllerApi.java
@@ -0,0 +1,750 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.multiparttest.request;
+
+import jakarta.annotation.Generated;
+import org.citrusframework.testapi.GeneratedApi;
+import org.citrusframework.testapi.GeneratedApiRequest;
+import jakarta.servlet.http.Cookie;
+import org.apache.commons.lang3.StringUtils;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.citrusframework.spi.Resources;
+import org.citrusframework.http.actions.HttpActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder.HttpMessageBuilderSupport;
+import org.citrusframework.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import org.citrusframework.openapi.generator.rest.multiparttest.citrus.MultipartTestAbstractTestRequest;
+
+import java.io.IOException;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class MultiparttestControllerApi implements GeneratedApi
+{
+
+ public static final MultiparttestControllerApi INSTANCE = new MultiparttestControllerApi();
+
+ public String getApiTitle() {
+ return "multiparttest API";
+ }
+
+ public String getApiVersion() {
+ return "2.0.0";
+ }
+
+ public String getApiPrefix() {
+ return "MultipartTest";
+ }
+
+ public Map getApiInfoExtensions() {
+ Map infoExtensionMap = new HashMap<>();
+ infoExtensionMap.put("x-citrus-api-name", "multiparttest-rest-resource");
+ infoExtensionMap.put("x-citrus-app", "MPT");
+ return infoExtensionMap;
+ }
+
+ /** deleteObject (DELETE /api/v2/multitest-file/{bucket}/{filename})
+ Delete file.
+
+ **/
+ public static class DeleteObjectRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/api/v2/multitest-file/{bucket}/{filename}";
+ private final Logger coverageLogger = LoggerFactory.getLogger(DeleteObjectRequest.class);
+
+ private String bucket;
+
+ private String filename;
+
+
+ public DeleteObjectRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("MultipartTest".toLowerCase() + ":deleteObjectRequestType");
+ }
+
+ public String getOperationName() {
+ return "deleteObject";
+ }
+
+ public String getMethod() {
+ return "DELETE";
+ }
+
+ public String getPath() {
+ return "/api/v2/multitest-file/{bucket}/{filename}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .delete(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "deleteObject;DELETE;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setBucket(String bucket) {
+ this.bucket = bucket;
+ }
+
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "bucket" + "}", bucket);endpoint = endpoint.replace("{" + "filename" + "}", filename);
+ return endpoint;
+ }
+ }
+ /** fileExists (GET /api/v2/multitest-file/{bucket}/{filename}/exists)
+ Checks if file exist.
+
+ **/
+ public static class FileExistsRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/api/v2/multitest-file/{bucket}/{filename}/exists";
+ private final Logger coverageLogger = LoggerFactory.getLogger(FileExistsRequest.class);
+
+ private String bucket;
+
+ private String filename;
+
+
+ public FileExistsRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("MultipartTest".toLowerCase() + ":fileExistsRequestType");
+ }
+
+ public String getOperationName() {
+ return "fileExists";
+ }
+
+ public String getMethod() {
+ return "GET";
+ }
+
+ public String getPath() {
+ return "/api/v2/multitest-file/{bucket}/{filename}/exists";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .get(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "fileExists;GET;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setBucket(String bucket) {
+ this.bucket = bucket;
+ }
+
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "bucket" + "}", bucket);endpoint = endpoint.replace("{" + "filename" + "}", filename);
+ return endpoint;
+ }
+ }
+ /** generateReport (POST /api/v2/multitest-reportgeneration)
+ summary
+
+ **/
+ public static class GenerateReportRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/api/v2/multitest-reportgeneration";
+ private final Logger coverageLogger = LoggerFactory.getLogger(GenerateReportRequest.class);
+
+ private String template;
+
+ private String additionalData;
+
+ private String _schema;
+
+
+ public GenerateReportRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("MultipartTest".toLowerCase() + ":generateReportRequestType");
+ }
+
+ public String getOperationName() {
+ return "generateReport";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/api/v2/multitest-reportgeneration";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .post(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ MultiValueMap multiValues = new LinkedMultiValueMap<>();
+ if(StringUtils.isBlank(template)) {
+ throw new CitrusRuntimeException(String.format("Required attribute '%s' is not specified", "template"));
+ }
+ if (StringUtils.isNotBlank(template)) {
+ // first try to load from resource
+ ClassPathResource resource = null;
+ try {
+ resource = new ClassPathResource(template);
+ }
+ catch(Exception ignore) {
+ // Use plain text instead of resource
+ }
+
+ if(resource != null && resource.exists()){
+ multiValues.add("template", resource);
+ } else {
+ multiValues.add("template", template);
+ }
+ bodyLog += template.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
+ }
+ if (StringUtils.isNotBlank(additionalData)) {
+ // first try to load from resource
+ ClassPathResource resource = null;
+ try {
+ resource = new ClassPathResource(additionalData);
+ }
+ catch(Exception ignore) {
+ // Use plain text instead of resource
+ }
+
+ if(resource != null && resource.exists()){
+ multiValues.add("additionalData", resource);
+ } else {
+ multiValues.add("additionalData", additionalData);
+ }
+ bodyLog += additionalData.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
+ }
+ if (StringUtils.isNotBlank(_schema)) {
+ // first try to load from resource
+ ClassPathResource resource = null;
+ try {
+ resource = new ClassPathResource(_schema);
+ }
+ catch(Exception ignore) {
+ // Use plain text instead of resource
+ }
+
+ if(resource != null && resource.exists()){
+ multiValues.add("_schema", resource);
+ } else {
+ multiValues.add("_schema", _schema);
+ }
+ bodyLog += _schema.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
+ }
+
+ bodyLog += "\";\"" + MediaType.MULTIPART_FORM_DATA_VALUE + "\"";
+ messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
+ .body(multiValues);
+
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "generateReport;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setTemplate(String template) {
+ this.template = template;
+ }
+
+ public void setAdditionalData(String additionalData) {
+ this.additionalData = additionalData;
+ }
+
+ public void set_schema(String _schema) {
+ this._schema = _schema;
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** multipleDatatypes (POST /api/v2/multitest-multipledatatypes)
+ summary
+
+ **/
+ public static class MultipleDatatypesRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/api/v2/multitest-multipledatatypes";
+ private final Logger coverageLogger = LoggerFactory.getLogger(MultipleDatatypesRequest.class);
+
+ private String stringData;
+
+ private String booleanData;
+
+ private String integerData;
+
+
+ public MultipleDatatypesRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("MultipartTest".toLowerCase() + ":multipleDatatypesRequestType");
+ }
+
+ public String getOperationName() {
+ return "multipleDatatypes";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/api/v2/multitest-multipledatatypes";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .post(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ MultiValueMap multiValues = new LinkedMultiValueMap<>();
+ if (StringUtils.isNotBlank(stringData)) {
+ // first try to load from resource
+ ClassPathResource resource = null;
+ try {
+ resource = new ClassPathResource(stringData);
+ }
+ catch(Exception ignore) {
+ // Use plain text instead of resource
+ }
+
+ if(resource != null && resource.exists()){
+ multiValues.add("stringData", resource);
+ } else {
+ multiValues.add("stringData", stringData);
+ }
+ bodyLog += stringData.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
+ }
+ if (StringUtils.isNotBlank(booleanData)) {
+ // first try to load from resource
+ ClassPathResource resource = null;
+ try {
+ resource = new ClassPathResource(booleanData);
+ }
+ catch(Exception ignore) {
+ // Use plain text instead of resource
+ }
+
+ if(resource != null && resource.exists()){
+ multiValues.add("booleanData", resource);
+ } else {
+ multiValues.add("booleanData", booleanData);
+ }
+ bodyLog += booleanData.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
+ }
+ if (StringUtils.isNotBlank(integerData)) {
+ // first try to load from resource
+ ClassPathResource resource = null;
+ try {
+ resource = new ClassPathResource(integerData);
+ }
+ catch(Exception ignore) {
+ // Use plain text instead of resource
+ }
+
+ if(resource != null && resource.exists()){
+ multiValues.add("integerData", resource);
+ } else {
+ multiValues.add("integerData", integerData);
+ }
+ bodyLog += integerData.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
+ }
+
+ bodyLog += "\";\"" + MediaType.MULTIPART_FORM_DATA_VALUE + "\"";
+ messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
+ .body(multiValues);
+
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "multipleDatatypes;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setStringData(String stringData) {
+ this.stringData = stringData;
+ }
+
+ public void setBooleanData(String booleanData) {
+ this.booleanData = booleanData;
+ }
+
+ public void setIntegerData(String integerData) {
+ this.integerData = integerData;
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** postFile (POST /api/v2/multitest-file/{bucket}/{filename})
+ Uploads file.
+
+ **/
+ public static class PostFileRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/api/v2/multitest-file/{bucket}/{filename}";
+ private final Logger coverageLogger = LoggerFactory.getLogger(PostFileRequest.class);
+
+ private String bucket;
+
+ private String filename;
+
+ private String multipartFile;
+
+
+ public PostFileRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("MultipartTest".toLowerCase() + ":postFileRequestType");
+ }
+
+ public String getOperationName() {
+ return "postFile";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/api/v2/multitest-file/{bucket}/{filename}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .post(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ MultiValueMap multiValues = new LinkedMultiValueMap<>();
+ if (StringUtils.isNotBlank(multipartFile)) {
+ multiValues.add("multipartFile", new ClassPathResource(multipartFile));
+ bodyLog += multipartFile.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
+ }
+
+ bodyLog += "\";\"" + MediaType.MULTIPART_FORM_DATA_VALUE + "\"";
+ messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
+ .body(multiValues);
+
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "postFile;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setBucket(String bucket) {
+ this.bucket = bucket;
+ }
+
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+ public void setMultipartFile(String multipartFile) {
+ this.multipartFile = multipartFile;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "bucket" + "}", bucket);endpoint = endpoint.replace("{" + "filename" + "}", filename);
+ return endpoint;
+ }
+ }
+ /** postRandom (POST /api/v2/multitest-file/{bucket}/{filename}/random)
+ Uploads random file.
+
+ **/
+ public static class PostRandomRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/api/v2/multitest-file/{bucket}/{filename}/random";
+ private final Logger coverageLogger = LoggerFactory.getLogger(PostRandomRequest.class);
+
+ private String bucket;
+
+ private String filename;
+
+
+ public PostRandomRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("MultipartTest".toLowerCase() + ":postRandomRequestType");
+ }
+
+ public String getOperationName() {
+ return "postRandom";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/api/v2/multitest-file/{bucket}/{filename}/random";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .post(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "postRandom;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setBucket(String bucket) {
+ this.bucket = bucket;
+ }
+
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "bucket" + "}", bucket);endpoint = endpoint.replace("{" + "filename" + "}", filename);
+ return endpoint;
+ }
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/spring/MultipartTestBeanConfiguration.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/spring/MultipartTestBeanConfiguration.java
new file mode 100644
index 0000000000..090a9bfb7e
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/spring/MultipartTestBeanConfiguration.java
@@ -0,0 +1,56 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.multiparttest.spring;
+
+import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
+
+import org.citrusframework.openapi.generator.rest.multiparttest.request.MultiparttestControllerApi;
+import javax.annotation.processing.Generated;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Scope;
+
+@Configuration
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class MultipartTestBeanConfiguration {
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public MultiparttestControllerApi.DeleteObjectRequest deleteObjectRequest() {
+ return new MultiparttestControllerApi.DeleteObjectRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public MultiparttestControllerApi.FileExistsRequest fileExistsRequest() {
+ return new MultiparttestControllerApi.FileExistsRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public MultiparttestControllerApi.GenerateReportRequest generateReportRequest() {
+ return new MultiparttestControllerApi.GenerateReportRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public MultiparttestControllerApi.MultipleDatatypesRequest multipleDatatypesRequest() {
+ return new MultiparttestControllerApi.MultipleDatatypesRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public MultiparttestControllerApi.PostFileRequest postFileRequest() {
+ return new MultiparttestControllerApi.PostFileRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public MultiparttestControllerApi.PostRandomRequest postRandomRequest() {
+ return new MultiparttestControllerApi.PostRandomRequest();
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreAbstractTestRequest.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreAbstractTestRequest.java
new file mode 100644
index 0000000000..9ff2150d21
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreAbstractTestRequest.java
@@ -0,0 +1,245 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.petstore.citrus;
+
+
+import static org.springframework.util.CollectionUtils.isEmpty;
+
+import jakarta.annotation.Generated;
+import jakarta.annotation.Nullable;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import javax.sql.DataSource;
+import org.citrusframework.actions.AbstractTestAction;
+import org.citrusframework.actions.ReceiveMessageAction;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.http.actions.HttpActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder;
+import org.citrusframework.http.actions.HttpClientResponseActionBuilder;
+import org.citrusframework.http.actions.HttpClientResponseActionBuilder.HttpMessageBuilderSupport;
+import org.citrusframework.http.client.HttpClient;
+import org.citrusframework.message.Message;
+import org.citrusframework.testapi.ApiActionBuilderCustomizerService;
+import org.citrusframework.testapi.GeneratedApi;
+import org.citrusframework.spi.Resources;
+import org.citrusframework.validation.DelegatingPayloadVariableExtractor;
+import org.citrusframework.validation.PathExpressionValidationContext;
+import org.citrusframework.validation.json.JsonMessageValidationContext;
+import org.citrusframework.validation.script.ScriptValidationContext;
+import org.slf4j.Marker;
+import org.slf4j.MarkerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public abstract class PetStoreAbstractTestRequest extends AbstractTestAction {
+
+ protected final Marker coverageMarker = MarkerFactory.getMarker("PETSTORE-API-COVERAGE");
+
+ @Autowired
+ @Qualifier("petStoreEndpoint")
+ protected HttpClient httpClient;
+
+ @Autowired(required = false)
+ protected DataSource dataSource;
+
+ @Autowired(required = false)
+ private List actionBuilderCustomizerServices;
+
+ // attributes of differentNodes
+ protected boolean schemaValidation;
+ protected String schema;
+ protected String bodyContentType;
+ protected String bodyLiteralContentType;
+ protected String bodyFile;
+ protected String bodyLiteral;
+ protected String responseAcceptType = "*/*";
+ protected String responseType = "json";
+ protected int responseStatus = 200;
+ protected String responseReasonPhrase = "OK";
+ protected String responseVersion = "HTTP/1.1";
+
+ // children of response element
+ protected String resource;
+ protected Map responseVariable; // Contains the 'JSON-PATH' as key and the 'VARIABLE NAME' as value
+ protected Map responseValue; // Contains the 'JSON-PATH' as key and the 'VALUE TO BE VALIDATED' as value
+ protected Map cookies;
+ protected Map headers;
+ protected String script;
+ protected String type; // default script type is groovy - supported types see com.consol.citrus.script.ScriptTypes
+
+ @Override
+ public void doExecute(TestContext context) {
+ sendRequest(context);
+ recieveResponse(context);
+ }
+
+ /**
+ * This method receives the HTTP-Response.
+ *
+ * @deprecated use {@link PetStoreAbstractTestRequest#receiveResponse(TestContext)} instead.
+ */
+ public ReceiveMessageAction recieveResponse(TestContext context) {
+
+ HttpClientResponseActionBuilder httpClientResponseActionBuilder = new HttpActionBuilder().client(httpClient).receive().response();
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientResponseActionBuilder.getMessageBuilderSupport();
+
+ messageBuilderSupport
+ .statusCode(responseStatus)
+ .reasonPhrase(responseReasonPhrase)
+ .version(responseVersion)
+ .validate(new JsonMessageValidationContext.Builder().schemaValidation(schemaValidation).schema(schema));
+
+ if (resource != null) {
+ messageBuilderSupport.body(Resources.create(resource));
+ }
+
+ if (!isEmpty(responseVariable)) {
+ DelegatingPayloadVariableExtractor.Builder extractorBuilder = new DelegatingPayloadVariableExtractor.Builder();
+ responseVariable.forEach(extractorBuilder::expression);
+ messageBuilderSupport.extract(extractorBuilder);
+ }
+
+ if (!isEmpty(responseValue)) {
+ PathExpressionValidationContext.Builder validationContextBuilder = new PathExpressionValidationContext.Builder();
+ responseValue.forEach(validationContextBuilder::expression);
+ messageBuilderSupport.validate(validationContextBuilder);
+ }
+
+ if (script != null) {
+ ScriptValidationContext.Builder scriptValidationContextBuilder = new ScriptValidationContext.Builder();
+ if (type != null) {
+ scriptValidationContextBuilder.scriptType(type);
+ }
+ scriptValidationContextBuilder.script(script);
+ messageBuilderSupport.validate(scriptValidationContextBuilder);
+ }
+
+ messageBuilderSupport.type(responseType);
+ httpClientResponseActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ var responseAction = httpClientResponseActionBuilder.build();
+
+ responseAction.execute(context);
+
+ return responseAction;
+ }
+
+ public @Nullable Message receiveResponse(TestContext context) {
+ var responseAction = recieveResponse(context);
+
+ var messageStore = context.getMessageStore();
+ return messageStore.getMessage(messageStore.constructMessageName(responseAction, httpClient));
+ }
+
+ public abstract void sendRequest(TestContext context);
+
+ public void setSchemaValidation(boolean schemaValidation) {
+ this.schemaValidation = schemaValidation;
+ }
+
+ public void setSchema(String schema) {
+ this.schema = schema;
+ }
+
+ public void setBodyLiteral(String bodyLiteral) {
+ this.bodyLiteral = bodyLiteral;
+ }
+
+ public void setBodyContentType(String bodyContentType) {
+ this.bodyContentType = bodyContentType;
+ }
+
+ public void setBodyLiteralContentType(String bodyLiteralContentType) {
+ this.bodyLiteralContentType = bodyLiteralContentType;
+ }
+
+ public void setResponseAcceptType(String responseAcceptType) {
+ this.responseAcceptType = responseAcceptType;
+ }
+
+ public void setCookie(Map cookies) {
+ this.cookies = cookies;
+ }
+
+ public void setHeader(Map headers) {
+ this.headers = headers;
+ }
+
+ public void setBodyFile(String bodyFile) {
+ this.bodyFile = bodyFile;
+ }
+
+ public void setResponseType(String responseType) {
+ this.responseType = responseType;
+ }
+
+ public void setResponseStatus(int responseStatus) {
+ this.responseStatus = responseStatus;
+ }
+
+ public void setResponseReasonPhrase(String responseReasonPhrase) {
+ this.responseReasonPhrase = responseReasonPhrase;
+ }
+
+ public void setResponseVersion(String responseVersion) {
+ this.responseVersion = responseVersion;
+ }
+
+ public void setResource(String resource) {
+ this.resource = resource;
+ }
+
+ public void setResponseVariable(Map responseVariable) {
+ this.responseVariable = responseVariable;
+ }
+
+ public void setResponseValue(Map responseValue) {
+ this.responseValue = responseValue;
+ }
+
+ public void setScript(String script) {
+ this.script = script;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ protected HttpClientRequestActionBuilder customizeBuilder(GeneratedApi generatedApi,
+ TestContext context, HttpClientRequestActionBuilder httpClientRequestActionBuilder) {
+
+ httpClientRequestActionBuilder = customizeByBeans(generatedApi, context,
+ httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder = customizeBySpi(generatedApi, context, httpClientRequestActionBuilder);
+
+ return httpClientRequestActionBuilder;
+ }
+
+ private HttpClientRequestActionBuilder customizeBySpi(GeneratedApi generatedApi, TestContext context,
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder) {
+ ServiceLoader serviceLoader = ServiceLoader.load(
+ ApiActionBuilderCustomizerService.class, ApiActionBuilderCustomizerService.class.getClassLoader());
+ for (ApiActionBuilderCustomizerService service :serviceLoader) {
+ httpClientRequestActionBuilder = service.build(generatedApi, this, context, httpClientRequestActionBuilder);
+ }
+ return httpClientRequestActionBuilder;
+ }
+
+ private HttpClientRequestActionBuilder customizeByBeans(
+ GeneratedApi generatedApi, TestContext context,
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder) {
+ if (actionBuilderCustomizerServices != null) {
+ for (ApiActionBuilderCustomizerService apiActionBuilderCustomizer : actionBuilderCustomizerServices) {
+ httpClientRequestActionBuilder = apiActionBuilderCustomizer.build(generatedApi, this,
+ context, httpClientRequestActionBuilder);
+ }
+ }
+ return httpClientRequestActionBuilder;
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreBeanDefinitionParser.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreBeanDefinitionParser.java
new file mode 100644
index 0000000000..32920fb8ef
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreBeanDefinitionParser.java
@@ -0,0 +1,215 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.petstore.citrus;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.processing.Generated;
+
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.core.Conventions;
+import org.springframework.util.Assert;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.xml.DomUtils;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class PetStoreBeanDefinitionParser implements BeanDefinitionParser {
+
+ private static final String COOKIE = "cookie";
+ private static final String HEADER = "header";
+ private static final String SOAP_HEADER = "soapHeader";
+ private static final String MIME_HEADER = "mimeHeader";
+ private static final String NAME = "name";
+ private static final String REQUEST_BODY = "body";
+ private static final String REQUEST_BODY_LITERAL = "bodyLiteral";
+ private static final String MULTIPART_BODY = "multipartBody";
+ private static final String RESPONSE = "response";
+ private static final String RESPONSE_JSONPATH = "json-path";
+ private static final String RESPONSE_XPATH = "xpath";
+ private static final String EXPRESSION = "expression";
+ private static final String VALUE = "value";
+ private static final String RESPONSE_RESOURCE = "resource";
+ private static final String FILE = "file";
+ private static final String RESPONSE_VARIABLE = "responseVariable";
+ private static final String RESPONSE_VALUE = "responseValue";
+ private static final String SCRIPT = "script";
+ private static final String TYPE = "type";
+ private static final String SQL = "sql";
+ private static final String COLUMN = "column";
+ private static final String VARIABLE = "variable";
+ // new
+ private static final String SCHEMA = "schema";
+ // new
+ private static final String SCHEMA_VALIDATION = "schemaValidation";
+
+ private final Class> beanClass;
+
+ public PetStoreBeanDefinitionParser(Class> beanClass) {
+ this.beanClass = beanClass;
+ }
+
+ public BeanDefinition parse(Element element) {
+ return parse(element, null);
+ }
+
+ /**
+ * Note: The {@link PetStoreBeanDefinitionParser#parse(Element element)} allows access direct
+ * access without the {@link org.springframework.beans.factory.xml.ParserContext} for convenience.
+ */
+ @Override
+ public BeanDefinition parse(Element element, ParserContext parserContext) {
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClass);
+ retrieveRootNodeAttributes(element, builder);
+ retrieveOptionalNodeAttributes(element, REQUEST_BODY, builder);
+ retrieveTextContentAndNodeAttributes(element, REQUEST_BODY_LITERAL, builder);
+ retrieveOptionalNodeAttributes(element, RESPONSE, builder);
+ retrieveParamNodeData(element, builder, COOKIE);
+ retrieveParamNodeData(element, builder, HEADER);
+ retrieveParamNodeData(element, builder, SOAP_HEADER);
+ retrieveParamNodeData(element, builder, MIME_HEADER);
+ retrieveOptionalNodeAttributes(element, SCHEMA, builder);
+ retrieveOptionalNodeAttributes(element, SCHEMA_VALIDATION, builder);
+ retrieveOptionalMultipartElements(element, builder);
+ retrieveResponseNodeData(element, builder);
+ builder.addPropertyValue("name", element.getTagName());
+ return builder.getBeanDefinition();
+ }
+
+ private void retrieveOptionalMultipartElements(Element element, BeanDefinitionBuilder builder) {
+ var multipartBodyElement = DomUtils.getChildElementByTagName(element, MULTIPART_BODY);
+ if (multipartBodyElement != null) {
+ var multipartBodyChildElements = DomUtils.getChildElements(multipartBodyElement);
+ for(int i = 0; i < multipartBodyChildElements.size(); i++){
+ var multipartBodyChildElement = multipartBodyChildElements.get(i);
+ String propertyName = Conventions.attributeNameToPropertyName(multipartBodyChildElement.getLocalName());
+ builder.addPropertyValue(propertyName, multipartBodyChildElement.getTextContent());
+ }
+ }
+ }
+
+ private void retrieveRootNodeAttributes(Element element, BeanDefinitionBuilder builder) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty.");
+ builder.addPropertyValue(propertyName, attribute.getValue());
+ }
+ }
+
+ private void retrieveOptionalNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) {
+ if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) {
+ Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ NamedNodeMap attributes = el.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty.");
+ String variableName = el.getLocalName() + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
+ builder.addPropertyValue(variableName, attribute.getValue());
+ }
+ }
+ }
+
+ private void retrieveTextContentAndNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) {
+ if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) {
+ Element el1 = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ NamedNodeMap attributes = el1.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName1 = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName1), "Illegal property name returned, it must not be null or empty.");
+ String variableName = el1.getLocalName() + propertyName1.substring(0, 1).toUpperCase() + propertyName1.substring(1);
+ builder.addPropertyValue(variableName, attribute.getValue());
+ }
+ Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ builder.addPropertyValue(elementName, el.getTextContent());
+ }
+ }
+
+ private void retrieveParamNodeData(Element element, BeanDefinitionBuilder builder, String paramType) {
+ if (!DomUtils.getChildElementsByTagName(element, paramType).isEmpty()) {
+ Map params = new HashMap<>();
+ List elements = DomUtils.getChildElementsByTagName(element, paramType);
+ elements.forEach(e -> {
+ String name = e.getAttribute(NAME);
+ String value = e.getAttribute(VALUE);
+
+ Assert.state(StringUtils.isNotBlank(name), "Illegal attribute value returned. The 'name' attribute must not be null or empty.");
+ Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty.");
+
+ params.put(name, value);
+ });
+ builder.addPropertyValue(paramType, params);
+ }
+ }
+
+ private void retrieveResponseNodeData(Element element, BeanDefinitionBuilder builder) {
+
+ if (!DomUtils.getChildElementsByTagName(element, RESPONSE).isEmpty()) {
+ Element response = DomUtils.getChildElementsByTagName(element, RESPONSE).get(0);
+ List elements = DomUtils.getChildElements(response);
+
+ Map responseVariable = new HashMap<>();
+ Map responseValue = new HashMap<>();
+
+ for (int i = 0; i < elements.size(); i++) {
+ Element e = elements.get(i);
+
+ if (e.getTagName().contains(RESPONSE_JSONPATH) || e.getTagName().contains(RESPONSE_XPATH)) {
+ String expression = e.getAttribute(EXPRESSION);
+ String value = e.getAttribute(VALUE);
+
+ Assert.state(StringUtils.isNotBlank(expression), "Illegal attribute value returned. The 'expression' attribute must not be null or empty.");
+ Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty.");
+
+ // variable to save @variable('ebid')@ else value to validate
+ if (value.matches("\\@variable\\('.*'\\)\\@")) {
+ Matcher match = Pattern.compile("\\'(.*?)\\'").matcher(value);
+ if (match.find()) {
+ responseVariable.put(expression, value.substring(match.start() + 1, match.end() - 1));
+ }
+ } else {
+ responseValue.put(expression, value);
+ }
+ } else if (e.getTagName().contains(SCRIPT)) {
+ String script = e.getTextContent();
+ Assert.state(StringUtils.isNotBlank(script), "Illegal attribute value returned. The 'script' attribute must not be null or empty.");
+ builder.addPropertyValue(SCRIPT, script);
+
+ if (!e.getAttribute(TYPE).isEmpty()) {
+ String type = e.getAttribute(TYPE);
+ Assert.state(StringUtils.isNotBlank(type), "Illegal attribute value returned. The 'type' attribute must not be null or empty.");
+ builder.addPropertyValue(TYPE, type);
+ }
+ } else if (e.getTagName().contains(RESPONSE_RESOURCE)) {
+ String filePath = e.getAttribute(FILE);
+ Assert.state(StringUtils.isNotBlank(filePath), "Illegal attribute value returned. The 'file' attribute must not be null or empty.");
+ builder.addPropertyValue(RESPONSE_RESOURCE, filePath);
+ }
+
+ }
+
+ builder.addPropertyValue(RESPONSE_VARIABLE, responseVariable);
+ builder.addPropertyValue(RESPONSE_VALUE, responseValue);
+ }
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/extension/PetStoreNamespaceHandler.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/extension/PetStoreNamespaceHandler.java
new file mode 100644
index 0000000000..af5b731084
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/extension/PetStoreNamespaceHandler.java
@@ -0,0 +1,45 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.petstore.citrus.extension;
+
+import org.citrusframework.openapi.generator.rest.petstore.request.PetApi;
+import org.citrusframework.openapi.generator.rest.petstore.request.StoreApi;
+import org.citrusframework.openapi.generator.rest.petstore.request.UserApi;
+import org.citrusframework.openapi.generator.rest.petstore.citrus.PetStoreBeanDefinitionParser;
+
+import javax.annotation.processing.Generated;
+
+import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class PetStoreNamespaceHandler extends NamespaceHandlerSupport {
+
+ @Override
+ public void init() {
+ registerBeanDefinitionParser("addPetRequest", new PetStoreBeanDefinitionParser(PetApi.AddPetRequest.class));
+ registerBeanDefinitionParser("deletePetRequest", new PetStoreBeanDefinitionParser(PetApi.DeletePetRequest.class));
+ registerBeanDefinitionParser("findPetsByStatusRequest", new PetStoreBeanDefinitionParser(PetApi.FindPetsByStatusRequest.class));
+ registerBeanDefinitionParser("findPetsByTagsRequest", new PetStoreBeanDefinitionParser(PetApi.FindPetsByTagsRequest.class));
+ registerBeanDefinitionParser("getPetByIdRequest", new PetStoreBeanDefinitionParser(PetApi.GetPetByIdRequest.class));
+ registerBeanDefinitionParser("updatePetRequest", new PetStoreBeanDefinitionParser(PetApi.UpdatePetRequest.class));
+ registerBeanDefinitionParser("updatePetWithFormRequest", new PetStoreBeanDefinitionParser(PetApi.UpdatePetWithFormRequest.class));
+ registerBeanDefinitionParser("uploadFileRequest", new PetStoreBeanDefinitionParser(PetApi.UploadFileRequest.class));
+ registerBeanDefinitionParser("deleteOrderRequest", new PetStoreBeanDefinitionParser(StoreApi.DeleteOrderRequest.class));
+ registerBeanDefinitionParser("getInventoryRequest", new PetStoreBeanDefinitionParser(StoreApi.GetInventoryRequest.class));
+ registerBeanDefinitionParser("getOrderByIdRequest", new PetStoreBeanDefinitionParser(StoreApi.GetOrderByIdRequest.class));
+ registerBeanDefinitionParser("placeOrderRequest", new PetStoreBeanDefinitionParser(StoreApi.PlaceOrderRequest.class));
+ registerBeanDefinitionParser("createUserRequest", new PetStoreBeanDefinitionParser(UserApi.CreateUserRequest.class));
+ registerBeanDefinitionParser("createUsersWithArrayInputRequest", new PetStoreBeanDefinitionParser(UserApi.CreateUsersWithArrayInputRequest.class));
+ registerBeanDefinitionParser("createUsersWithListInputRequest", new PetStoreBeanDefinitionParser(UserApi.CreateUsersWithListInputRequest.class));
+ registerBeanDefinitionParser("deleteUserRequest", new PetStoreBeanDefinitionParser(UserApi.DeleteUserRequest.class));
+ registerBeanDefinitionParser("getUserByNameRequest", new PetStoreBeanDefinitionParser(UserApi.GetUserByNameRequest.class));
+ registerBeanDefinitionParser("loginUserRequest", new PetStoreBeanDefinitionParser(UserApi.LoginUserRequest.class));
+ registerBeanDefinitionParser("logoutUserRequest", new PetStoreBeanDefinitionParser(UserApi.LogoutUserRequest.class));
+ registerBeanDefinitionParser("updateUserRequest", new PetStoreBeanDefinitionParser(UserApi.UpdateUserRequest.class));
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Category.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Category.java
new file mode 100644
index 0000000000..d5341fea2c
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Category.java
@@ -0,0 +1 @@
+// not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/ModelApiResponse.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/ModelApiResponse.java
new file mode 100644
index 0000000000..d5341fea2c
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/ModelApiResponse.java
@@ -0,0 +1 @@
+// not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Order.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Order.java
new file mode 100644
index 0000000000..d5341fea2c
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Order.java
@@ -0,0 +1 @@
+// not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Pet.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Pet.java
new file mode 100644
index 0000000000..d5341fea2c
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Pet.java
@@ -0,0 +1 @@
+// not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Tag.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Tag.java
new file mode 100644
index 0000000000..d5341fea2c
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Tag.java
@@ -0,0 +1 @@
+// not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/User.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/User.java
new file mode 100644
index 0000000000..d5341fea2c
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/User.java
@@ -0,0 +1 @@
+// not in use
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/PetApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/PetApi.java
new file mode 100644
index 0000000000..a014b1c53f
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/PetApi.java
@@ -0,0 +1,862 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.petstore.request;
+
+import jakarta.annotation.Generated;
+import org.citrusframework.testapi.GeneratedApi;
+import org.citrusframework.testapi.GeneratedApiRequest;
+import jakarta.servlet.http.Cookie;
+import org.apache.commons.lang3.StringUtils;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.citrusframework.spi.Resources;
+import org.citrusframework.http.actions.HttpActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder.HttpMessageBuilderSupport;
+import org.citrusframework.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import org.citrusframework.openapi.generator.rest.petstore.citrus.PetStoreAbstractTestRequest;
+
+import java.io.IOException;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class PetApi implements GeneratedApi
+{
+
+ public static final PetApi INSTANCE = new PetApi();
+
+ public String getApiTitle() {
+ return "OpenAPI Petstore";
+ }
+
+ public String getApiVersion() {
+ return "1.0.0";
+ }
+
+ public String getApiPrefix() {
+ return "PetStore";
+ }
+
+ public Map getApiInfoExtensions() {
+ Map infoExtensionMap = new HashMap<>();
+ infoExtensionMap.put("x-citrus-api-name", "petstore");
+ infoExtensionMap.put("x-citrus-app", "PETS");
+ return infoExtensionMap;
+ }
+
+ /** addPet (POST /pet)
+ Add a new pet to the store
+
+ **/
+ public static class AddPetRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/pet";
+ private final Logger coverageLogger = LoggerFactory.getLogger(AddPetRequest.class);
+
+
+ public AddPetRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":addPetRequestType");
+ }
+
+ public String getOperationName() {
+ return "addPet";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/pet";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .post(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "addPet;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** deletePet (DELETE /pet/{petId})
+ Deletes a pet
+
+ **/
+ public static class DeletePetRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/pet/{petId}";
+ private final Logger coverageLogger = LoggerFactory.getLogger(DeletePetRequest.class);
+
+ private String petId;
+
+
+ public DeletePetRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":deletePetRequestType");
+ }
+
+ public String getOperationName() {
+ return "deletePet";
+ }
+
+ public String getMethod() {
+ return "DELETE";
+ }
+
+ public String getPath() {
+ return "/pet/{petId}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .delete(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "deletePet;DELETE;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setPetId(String petId) {
+ this.petId = petId;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "petId" + "}", petId);
+ return endpoint;
+ }
+ }
+ /** findPetsByStatus (GET /pet/findByStatus)
+ Finds Pets by status
+
+ **/
+ public static class FindPetsByStatusRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/pet/findByStatus";
+ private final Logger coverageLogger = LoggerFactory.getLogger(FindPetsByStatusRequest.class);
+
+ private String status;
+
+
+ public FindPetsByStatusRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":findPetsByStatusRequestType");
+ }
+
+ public String getOperationName() {
+ return "findPetsByStatus";
+ }
+
+ public String getMethod() {
+ return "GET";
+ }
+
+ public String getPath() {
+ return "/pet/findByStatus";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .get(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+
+ if (StringUtils.isNotBlank(this.status)) {
+ queryParams.put("status", context.replaceDynamicContentInString(this.status));
+ httpClientRequestActionBuilder.queryParam("status", this.status);
+ }
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "findPetsByStatus;GET;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** findPetsByTags (GET /pet/findByTags)
+ Finds Pets by tags
+
+ **/
+ public static class FindPetsByTagsRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/pet/findByTags";
+ private final Logger coverageLogger = LoggerFactory.getLogger(FindPetsByTagsRequest.class);
+
+ private String tags;
+
+
+ public FindPetsByTagsRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":findPetsByTagsRequestType");
+ }
+
+ public String getOperationName() {
+ return "findPetsByTags";
+ }
+
+ public String getMethod() {
+ return "GET";
+ }
+
+ public String getPath() {
+ return "/pet/findByTags";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .get(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+
+ if (StringUtils.isNotBlank(this.tags)) {
+ queryParams.put("tags", context.replaceDynamicContentInString(this.tags));
+ httpClientRequestActionBuilder.queryParam("tags", this.tags);
+ }
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "findPetsByTags;GET;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setTags(String tags) {
+ this.tags = tags;
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** getPetById (GET /pet/{petId})
+ Find pet by ID
+
+ **/
+ public static class GetPetByIdRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/pet/{petId}";
+ private final Logger coverageLogger = LoggerFactory.getLogger(GetPetByIdRequest.class);
+
+ private String petId;
+
+
+ @Value("${" + "petStoreEndpoint.basic.username:#{null}}")
+ private String basicUsername;
+ @Value("${" + "petStoreEndpoint.basic.password:#{null}}")
+ private String basicPassword;
+
+
+ public GetPetByIdRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":getPetByIdRequestType");
+ }
+
+ public String getOperationName() {
+ return "getPetById";
+ }
+
+ public String getMethod() {
+ return "GET";
+ }
+
+ public String getPath() {
+ return "/pet/{petId}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .get(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+
+ if(basicUsername != null && basicPassword != null){
+ messageBuilderSupport.header("Authorization", "Basic " + Base64.getEncoder().encodeToString((context.replaceDynamicContentInString(basicUsername)+":"+context.replaceDynamicContentInString(basicPassword)).getBytes()));
+ }
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "getPetById;GET;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setPetId(String petId) {
+ this.petId = petId;
+ }
+
+
+ public void setBasicUsername(String basicUsername) {
+ this.basicUsername = basicUsername;
+ }
+
+ public void setBasicPassword(String basicPassword) {
+ this.basicPassword = basicPassword;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "petId" + "}", petId);
+ return endpoint;
+ }
+ }
+ /** updatePet (PUT /pet)
+ Update an existing pet
+
+ **/
+ public static class UpdatePetRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/pet";
+ private final Logger coverageLogger = LoggerFactory.getLogger(UpdatePetRequest.class);
+
+
+ public UpdatePetRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":updatePetRequestType");
+ }
+
+ public String getOperationName() {
+ return "updatePet";
+ }
+
+ public String getMethod() {
+ return "PUT";
+ }
+
+ public String getPath() {
+ return "/pet";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .put(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "updatePet;PUT;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** updatePetWithForm (POST /pet/{petId})
+ Updates a pet in the store with form data
+
+ **/
+ public static class UpdatePetWithFormRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/pet/{petId}";
+ private final Logger coverageLogger = LoggerFactory.getLogger(UpdatePetWithFormRequest.class);
+
+ private String petId;
+
+
+ public UpdatePetWithFormRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":updatePetWithFormRequestType");
+ }
+
+ public String getOperationName() {
+ return "updatePetWithForm";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/pet/{petId}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .post(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "updatePetWithForm;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setPetId(String petId) {
+ this.petId = petId;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "petId" + "}", petId);
+ return endpoint;
+ }
+ }
+ /** uploadFile (POST /pet/{petId}/uploadImage)
+ uploads an image
+
+ **/
+ public static class UploadFileRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/pet/{petId}/uploadImage";
+ private final Logger coverageLogger = LoggerFactory.getLogger(UploadFileRequest.class);
+
+ private String petId;
+
+ private String additionalMetadata;
+
+ private String _file;
+
+
+ public UploadFileRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":uploadFileRequestType");
+ }
+
+ public String getOperationName() {
+ return "uploadFile";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/pet/{petId}/uploadImage";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .post(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ MultiValueMap multiValues = new LinkedMultiValueMap<>();
+ if (StringUtils.isNotBlank(additionalMetadata)) {
+ // first try to load from resource
+ ClassPathResource resource = null;
+ try {
+ resource = new ClassPathResource(additionalMetadata);
+ }
+ catch(Exception ignore) {
+ // Use plain text instead of resource
+ }
+
+ if(resource != null && resource.exists()){
+ multiValues.add("additionalMetadata", resource);
+ } else {
+ multiValues.add("additionalMetadata", additionalMetadata);
+ }
+ bodyLog += additionalMetadata.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
+ }
+ if (StringUtils.isNotBlank(_file)) {
+ multiValues.add("_file", new ClassPathResource(_file));
+ bodyLog += _file.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
+ }
+
+ bodyLog += "\";\"" + MediaType.MULTIPART_FORM_DATA_VALUE + "\"";
+ messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
+ .body(multiValues);
+
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "uploadFile;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setPetId(String petId) {
+ this.petId = petId;
+ }
+
+ public void setAdditionalMetadata(String additionalMetadata) {
+ this.additionalMetadata = additionalMetadata;
+ }
+
+ public void set_file(String _file) {
+ this._file = _file;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "petId" + "}", petId);
+ return endpoint;
+ }
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/StoreApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/StoreApi.java
new file mode 100644
index 0000000000..406f97f2f9
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/StoreApi.java
@@ -0,0 +1,433 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.petstore.request;
+
+import jakarta.annotation.Generated;
+import org.citrusframework.testapi.GeneratedApi;
+import org.citrusframework.testapi.GeneratedApiRequest;
+import jakarta.servlet.http.Cookie;
+import org.apache.commons.lang3.StringUtils;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.citrusframework.spi.Resources;
+import org.citrusframework.http.actions.HttpActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder.HttpMessageBuilderSupport;
+import org.citrusframework.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import org.citrusframework.openapi.generator.rest.petstore.citrus.PetStoreAbstractTestRequest;
+
+import java.io.IOException;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class StoreApi implements GeneratedApi
+{
+
+ public static final StoreApi INSTANCE = new StoreApi();
+
+ public String getApiTitle() {
+ return "OpenAPI Petstore";
+ }
+
+ public String getApiVersion() {
+ return "1.0.0";
+ }
+
+ public String getApiPrefix() {
+ return "PetStore";
+ }
+
+ public Map getApiInfoExtensions() {
+ Map infoExtensionMap = new HashMap<>();
+ infoExtensionMap.put("x-citrus-api-name", "petstore");
+ infoExtensionMap.put("x-citrus-app", "PETS");
+ return infoExtensionMap;
+ }
+
+ /** deleteOrder (DELETE /store/order/{order_id})
+ Delete purchase order by ID
+
+ **/
+ public static class DeleteOrderRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/store/order/{order_id}";
+ private final Logger coverageLogger = LoggerFactory.getLogger(DeleteOrderRequest.class);
+
+ private String orderId;
+
+
+ public DeleteOrderRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":deleteOrderRequestType");
+ }
+
+ public String getOperationName() {
+ return "deleteOrder";
+ }
+
+ public String getMethod() {
+ return "DELETE";
+ }
+
+ public String getPath() {
+ return "/store/order/{order_id}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .delete(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "deleteOrder;DELETE;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setOrderId(String orderId) {
+ this.orderId = orderId;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "order_id" + "}", orderId);
+ return endpoint;
+ }
+ }
+ /** getInventory (GET /store/inventory)
+ Returns pet inventories by status
+
+ **/
+ public static class GetInventoryRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/store/inventory";
+ private final Logger coverageLogger = LoggerFactory.getLogger(GetInventoryRequest.class);
+
+
+ public GetInventoryRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":getInventoryRequestType");
+ }
+
+ public String getOperationName() {
+ return "getInventory";
+ }
+
+ public String getMethod() {
+ return "GET";
+ }
+
+ public String getPath() {
+ return "/store/inventory";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .get(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "getInventory;GET;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** getOrderById (GET /store/order/{order_id})
+ Find purchase order by ID
+
+ **/
+ public static class GetOrderByIdRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/store/order/{order_id}";
+ private final Logger coverageLogger = LoggerFactory.getLogger(GetOrderByIdRequest.class);
+
+ private String orderId;
+
+
+ public GetOrderByIdRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":getOrderByIdRequestType");
+ }
+
+ public String getOperationName() {
+ return "getOrderById";
+ }
+
+ public String getMethod() {
+ return "GET";
+ }
+
+ public String getPath() {
+ return "/store/order/{order_id}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .get(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "getOrderById;GET;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setOrderId(String orderId) {
+ this.orderId = orderId;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "order_id" + "}", orderId);
+ return endpoint;
+ }
+ }
+ /** placeOrder (POST /store/order)
+ Place an order for a pet
+
+ **/
+ public static class PlaceOrderRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/store/order";
+ private final Logger coverageLogger = LoggerFactory.getLogger(PlaceOrderRequest.class);
+
+
+ public PlaceOrderRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":placeOrderRequestType");
+ }
+
+ public String getOperationName() {
+ return "placeOrder";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/store/order";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .post(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "placeOrder;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/UserApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/UserApi.java
new file mode 100644
index 0000000000..01f4558954
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/UserApi.java
@@ -0,0 +1,819 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.petstore.request;
+
+import jakarta.annotation.Generated;
+import org.citrusframework.testapi.GeneratedApi;
+import org.citrusframework.testapi.GeneratedApiRequest;
+import jakarta.servlet.http.Cookie;
+import org.apache.commons.lang3.StringUtils;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.citrusframework.spi.Resources;
+import org.citrusframework.http.actions.HttpActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder.HttpMessageBuilderSupport;
+import org.citrusframework.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import org.citrusframework.openapi.generator.rest.petstore.citrus.PetStoreAbstractTestRequest;
+
+import java.io.IOException;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class UserApi implements GeneratedApi
+{
+
+ public static final UserApi INSTANCE = new UserApi();
+
+ public String getApiTitle() {
+ return "OpenAPI Petstore";
+ }
+
+ public String getApiVersion() {
+ return "1.0.0";
+ }
+
+ public String getApiPrefix() {
+ return "PetStore";
+ }
+
+ public Map getApiInfoExtensions() {
+ Map infoExtensionMap = new HashMap<>();
+ infoExtensionMap.put("x-citrus-api-name", "petstore");
+ infoExtensionMap.put("x-citrus-app", "PETS");
+ return infoExtensionMap;
+ }
+
+ /** createUser (POST /user)
+ Create user
+
+ **/
+ public static class CreateUserRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/user";
+ private final Logger coverageLogger = LoggerFactory.getLogger(CreateUserRequest.class);
+
+
+ public CreateUserRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":createUserRequestType");
+ }
+
+ public String getOperationName() {
+ return "createUser";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/user";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .post(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "createUser;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** createUsersWithArrayInput (POST /user/createWithArray)
+ Creates list of users with given input array
+
+ **/
+ public static class CreateUsersWithArrayInputRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/user/createWithArray";
+ private final Logger coverageLogger = LoggerFactory.getLogger(CreateUsersWithArrayInputRequest.class);
+
+
+ public CreateUsersWithArrayInputRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":createUsersWithArrayInputRequestType");
+ }
+
+ public String getOperationName() {
+ return "createUsersWithArrayInput";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/user/createWithArray";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .post(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "createUsersWithArrayInput;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** createUsersWithListInput (POST /user/createWithList)
+ Creates list of users with given input array
+
+ **/
+ public static class CreateUsersWithListInputRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/user/createWithList";
+ private final Logger coverageLogger = LoggerFactory.getLogger(CreateUsersWithListInputRequest.class);
+
+
+ public CreateUsersWithListInputRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":createUsersWithListInputRequestType");
+ }
+
+ public String getOperationName() {
+ return "createUsersWithListInput";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/user/createWithList";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .post(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "createUsersWithListInput;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** deleteUser (DELETE /user/{username})
+ Delete user
+
+ **/
+ public static class DeleteUserRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/user/{username}";
+ private final Logger coverageLogger = LoggerFactory.getLogger(DeleteUserRequest.class);
+
+ private String username;
+
+
+ public DeleteUserRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":deleteUserRequestType");
+ }
+
+ public String getOperationName() {
+ return "deleteUser";
+ }
+
+ public String getMethod() {
+ return "DELETE";
+ }
+
+ public String getPath() {
+ return "/user/{username}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .delete(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "deleteUser;DELETE;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "username" + "}", username);
+ return endpoint;
+ }
+ }
+ /** getUserByName (GET /user/{username})
+ Get user by user name
+
+ **/
+ public static class GetUserByNameRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/user/{username}";
+ private final Logger coverageLogger = LoggerFactory.getLogger(GetUserByNameRequest.class);
+
+ private String username;
+
+
+ public GetUserByNameRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":getUserByNameRequestType");
+ }
+
+ public String getOperationName() {
+ return "getUserByName";
+ }
+
+ public String getMethod() {
+ return "GET";
+ }
+
+ public String getPath() {
+ return "/user/{username}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .get(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "getUserByName;GET;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "username" + "}", username);
+ return endpoint;
+ }
+ }
+ /** loginUser (GET /user/login)
+ Logs user into the system
+
+ **/
+ public static class LoginUserRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/user/login";
+ private final Logger coverageLogger = LoggerFactory.getLogger(LoginUserRequest.class);
+
+ private String username;
+
+ private String password;
+
+
+ public LoginUserRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":loginUserRequestType");
+ }
+
+ public String getOperationName() {
+ return "loginUser";
+ }
+
+ public String getMethod() {
+ return "GET";
+ }
+
+ public String getPath() {
+ return "/user/login";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .get(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+
+ if (StringUtils.isNotBlank(this.username)) {
+ queryParams.put("username", context.replaceDynamicContentInString(this.username));
+ httpClientRequestActionBuilder.queryParam("username", this.username);
+ }
+
+
+ if (StringUtils.isNotBlank(this.password)) {
+ queryParams.put("password", context.replaceDynamicContentInString(this.password));
+ httpClientRequestActionBuilder.queryParam("password", this.password);
+ }
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "loginUser;GET;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** logoutUser (GET /user/logout)
+ Logs out current logged in user session
+
+ **/
+ public static class LogoutUserRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/user/logout";
+ private final Logger coverageLogger = LoggerFactory.getLogger(LogoutUserRequest.class);
+
+
+ public LogoutUserRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":logoutUserRequestType");
+ }
+
+ public String getOperationName() {
+ return "logoutUser";
+ }
+
+ public String getMethod() {
+ return "GET";
+ }
+
+ public String getPath() {
+ return "/user/logout";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .get(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "logoutUser;GET;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ private String replacePathParams(String endpoint) {
+
+ return endpoint;
+ }
+ }
+ /** updateUser (PUT /user/{username})
+ Updated user
+
+ **/
+ public static class UpdateUserRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest {
+
+ private static final String ENDPOINT = "/user/{username}";
+ private final Logger coverageLogger = LoggerFactory.getLogger(UpdateUserRequest.class);
+
+ private String username;
+
+
+ public UpdateUserRequest() {
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("PetStore".toLowerCase() + ":updateUserRequestType");
+ }
+
+ public String getOperationName() {
+ return "updateUser";
+ }
+
+ public String getMethod() {
+ return "PUT";
+ }
+
+ public String getPath() {
+ return "/user/{username}";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+ HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
+ .put(replacePathParams(ENDPOINT));
+
+ HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
+ messageBuilderSupport.accept(responseAcceptType);
+
+ if (cookies != null) {
+ cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ }
+
+ if (headers != null) {
+ headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
+ headers.forEach(messageBuilderSupport::header);
+ }
+
+ String bodyLog = "";
+ String payload = null;
+ String payloadType = null;
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
+
+ httpClientRequestActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "updateUser;PUT;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ bodyLog);
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ private String replacePathParams(String endpoint) {
+ endpoint = endpoint.replace("{" + "username" + "}", username);
+ return endpoint;
+ }
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/spring/PetStoreBeanConfiguration.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/spring/PetStoreBeanConfiguration.java
new file mode 100644
index 0000000000..cb89458c0b
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/spring/PetStoreBeanConfiguration.java
@@ -0,0 +1,142 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.rest.petstore.spring;
+
+import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
+
+import org.citrusframework.openapi.generator.rest.petstore.request.PetApi;
+import org.citrusframework.openapi.generator.rest.petstore.request.StoreApi;
+import org.citrusframework.openapi.generator.rest.petstore.request.UserApi;
+import javax.annotation.processing.Generated;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Scope;
+
+@Configuration
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class PetStoreBeanConfiguration {
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public PetApi.AddPetRequest addPetRequest() {
+ return new PetApi.AddPetRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public PetApi.DeletePetRequest deletePetRequest() {
+ return new PetApi.DeletePetRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public PetApi.FindPetsByStatusRequest findPetsByStatusRequest() {
+ return new PetApi.FindPetsByStatusRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public PetApi.FindPetsByTagsRequest findPetsByTagsRequest() {
+ return new PetApi.FindPetsByTagsRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public PetApi.GetPetByIdRequest getPetByIdRequest() {
+ return new PetApi.GetPetByIdRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public PetApi.UpdatePetRequest updatePetRequest() {
+ return new PetApi.UpdatePetRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public PetApi.UpdatePetWithFormRequest updatePetWithFormRequest() {
+ return new PetApi.UpdatePetWithFormRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public PetApi.UploadFileRequest uploadFileRequest() {
+ return new PetApi.UploadFileRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public StoreApi.DeleteOrderRequest deleteOrderRequest() {
+ return new StoreApi.DeleteOrderRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public StoreApi.GetInventoryRequest getInventoryRequest() {
+ return new StoreApi.GetInventoryRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public StoreApi.GetOrderByIdRequest getOrderByIdRequest() {
+ return new StoreApi.GetOrderByIdRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public StoreApi.PlaceOrderRequest placeOrderRequest() {
+ return new StoreApi.PlaceOrderRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public UserApi.CreateUserRequest createUserRequest() {
+ return new UserApi.CreateUserRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public UserApi.CreateUsersWithArrayInputRequest createUsersWithArrayInputRequest() {
+ return new UserApi.CreateUsersWithArrayInputRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public UserApi.CreateUsersWithListInputRequest createUsersWithListInputRequest() {
+ return new UserApi.CreateUsersWithListInputRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public UserApi.DeleteUserRequest deleteUserRequest() {
+ return new UserApi.DeleteUserRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public UserApi.GetUserByNameRequest getUserByNameRequest() {
+ return new UserApi.GetUserByNameRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public UserApi.LoginUserRequest loginUserRequest() {
+ return new UserApi.LoginUserRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public UserApi.LogoutUserRequest logoutUserRequest() {
+ return new UserApi.LogoutUserRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public UserApi.UpdateUserRequest updateUserRequest() {
+ return new UserApi.UpdateUserRequest();
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlAbstractTestRequest.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlAbstractTestRequest.java
new file mode 100644
index 0000000000..8acda64ca3
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlAbstractTestRequest.java
@@ -0,0 +1,187 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.soap.bookservice.citrus;
+
+import jakarta.annotation.Generated;
+import java.util.List;
+import java.util.ServiceLoader;
+import org.citrusframework.actions.AbstractTestAction;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.http.actions.HttpClientRequestActionBuilder;
+import org.citrusframework.testapi.ApiActionBuilderCustomizerService;
+import org.citrusframework.testapi.GeneratedApi;
+import org.citrusframework.spi.Resources;
+import org.citrusframework.validation.DelegatingPayloadVariableExtractor;
+import org.citrusframework.validation.PathExpressionValidationContext;
+import org.citrusframework.validation.script.ScriptValidationContext;
+import org.citrusframework.ws.actions.ReceiveSoapMessageAction;
+import org.citrusframework.ws.actions.ReceiveSoapMessageAction.SoapMessageBuilderSupport;
+import org.citrusframework.ws.actions.SendSoapMessageAction;
+import org.citrusframework.ws.actions.SoapActionBuilder;
+import org.citrusframework.ws.client.WebServiceClient;
+import org.slf4j.Marker;
+import org.slf4j.MarkerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.util.CollectionUtils;
+
+import javax.sql.DataSource;
+import java.util.Map;
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public abstract class OpenApiFromWsdlAbstractTestRequest extends AbstractTestAction {
+
+ protected final Marker coverageMarker = MarkerFactory.getMarker("OPENAPIFROMWSDL-API-COVERAGE");
+
+ @Autowired
+ @Qualifier("soapSampleEndpoint")
+ protected WebServiceClient wsClient;
+
+ @Autowired(required = false)
+ protected DataSource dataSource;
+
+ @Autowired(required = false)
+ private List actionBuilderCustomizerServices;
+
+ // attributes of differentNodes
+ protected String bodyContentType;
+ protected String bodyLiteralContentType;
+ protected String bodyFile;
+ protected String bodyLiteral;
+
+ // children of response element
+ protected String resource;
+ protected Map responseVariable; // Contains the 'XPATH' as key and the 'VARIABLE NAME' as value
+ protected Map responseValue; // Contains the 'XPATH' as key and the 'VALUE TO BE VALIDATED' as value
+ protected String script;
+ protected String type; // default script type is groovy - supported types see com.consol.citrus.script.ScriptTypes
+ protected Map soapHeaders;
+ protected Map mimeHeaders;
+
+ @Override
+ public void doExecute(TestContext context) {
+ sendRequest(context);
+ receiveResponse(context);
+ }
+
+ /**
+ * This method receives the HTTP-Response
+ */
+ public void receiveResponse(TestContext context) {
+
+ ReceiveSoapMessageAction.Builder soapReceiveMessageActionBuilder = new SoapActionBuilder().client(wsClient).receive();
+ SoapMessageBuilderSupport messageBuilderSupport = soapReceiveMessageActionBuilder.getMessageBuilderSupport();
+
+ if (resource != null) {
+ messageBuilderSupport.body(Resources.create(resource));
+ }
+
+ if (!CollectionUtils.isEmpty(responseVariable)) {
+ DelegatingPayloadVariableExtractor.Builder extractorBuilder = new DelegatingPayloadVariableExtractor.Builder();
+ responseVariable.forEach(extractorBuilder::expression);
+ messageBuilderSupport.extract(extractorBuilder);
+ }
+
+ if (!CollectionUtils.isEmpty(responseValue)) {
+ PathExpressionValidationContext.Builder validationContextBuilder = new PathExpressionValidationContext.Builder();
+ responseValue.forEach(validationContextBuilder::expression);
+ messageBuilderSupport.validate(validationContextBuilder);
+ }
+
+ if (script != null) {
+ ScriptValidationContext.Builder scriptValidationContextBuilder = new ScriptValidationContext.Builder();
+ if (type != null) {
+ scriptValidationContextBuilder.scriptType(type);
+ }
+ scriptValidationContextBuilder.script(script);
+ messageBuilderSupport.validate(scriptValidationContextBuilder);
+ }
+
+ soapReceiveMessageActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ soapReceiveMessageActionBuilder.build().execute(context);
+ }
+
+ public abstract void sendRequest(TestContext context);
+
+ public void setBodyLiteral(String bodyLiteral) {
+ this.bodyLiteral = bodyLiteral;
+ }
+ public void setBodyContentType(String bodyContentType) {
+ this.bodyContentType = bodyContentType;
+ }
+
+ public void setBodyLiteralContentType(String bodyLiteralContentType) {
+ this.bodyLiteralContentType = bodyLiteralContentType;
+ }
+
+ public void setBodyFile(String bodyFile) {
+ this.bodyFile = bodyFile;
+ }
+
+ public void setResource(String resource) {
+ this.resource = resource;
+ }
+
+ public void setResponseVariable(Map responseVariable) {
+ this.responseVariable = responseVariable;
+ }
+
+ public void setResponseValue(Map responseValue) {
+ this.responseValue = responseValue;
+ }
+
+ public void setScript(String script) {
+ this.script = script;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public void setSoapHeader(Map soapHeaders) {
+ this.soapHeaders = soapHeaders;
+ }
+
+ public void setMimeHeader(Map mimeHeaders) {
+ this.mimeHeaders = mimeHeaders;
+ }
+
+ protected SendSoapMessageAction.Builder customizeBuilder(GeneratedApi generatedApi,
+ TestContext context, SendSoapMessageAction.Builder sendSoapMessageActionBuilder) {
+
+ sendSoapMessageActionBuilder = customizeByBeans(generatedApi, context, sendSoapMessageActionBuilder);
+
+ sendSoapMessageActionBuilder = customizeBySpi(generatedApi, context, sendSoapMessageActionBuilder);
+
+ return sendSoapMessageActionBuilder;
+ }
+
+ private SendSoapMessageAction.Builder customizeBySpi(GeneratedApi generatedApi, TestContext context,
+ SendSoapMessageAction.Builder sendSoapMessageActionBuilder) {
+
+ ServiceLoader serviceLoader = ServiceLoader.load(
+ ApiActionBuilderCustomizerService.class, ApiActionBuilderCustomizerService.class.getClassLoader());
+ for (ApiActionBuilderCustomizerService service :serviceLoader) {
+ sendSoapMessageActionBuilder = service.build(generatedApi, this, context, sendSoapMessageActionBuilder);
+ }
+
+ return sendSoapMessageActionBuilder;
+ }
+
+ private SendSoapMessageAction.Builder customizeByBeans(
+ GeneratedApi generatedApi, TestContext context, SendSoapMessageAction.Builder sendSoapMessageActionBuilder) {
+
+ if (actionBuilderCustomizerServices != null) {
+ for (ApiActionBuilderCustomizerService apiActionBuilderCustomizer : actionBuilderCustomizerServices) {
+ sendSoapMessageActionBuilder = apiActionBuilderCustomizer.build(generatedApi, this,
+ context, sendSoapMessageActionBuilder);
+ }
+ }
+
+ return sendSoapMessageActionBuilder;
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlBeanDefinitionParser.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlBeanDefinitionParser.java
new file mode 100644
index 0000000000..1c72d58016
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlBeanDefinitionParser.java
@@ -0,0 +1,215 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.soap.bookservice.citrus;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.processing.Generated;
+
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.core.Conventions;
+import org.springframework.util.Assert;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.xml.DomUtils;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class OpenApiFromWsdlBeanDefinitionParser implements BeanDefinitionParser {
+
+ private static final String COOKIE = "cookie";
+ private static final String HEADER = "header";
+ private static final String SOAP_HEADER = "soapHeader";
+ private static final String MIME_HEADER = "mimeHeader";
+ private static final String NAME = "name";
+ private static final String REQUEST_BODY = "body";
+ private static final String REQUEST_BODY_LITERAL = "bodyLiteral";
+ private static final String MULTIPART_BODY = "multipartBody";
+ private static final String RESPONSE = "response";
+ private static final String RESPONSE_JSONPATH = "json-path";
+ private static final String RESPONSE_XPATH = "xpath";
+ private static final String EXPRESSION = "expression";
+ private static final String VALUE = "value";
+ private static final String RESPONSE_RESOURCE = "resource";
+ private static final String FILE = "file";
+ private static final String RESPONSE_VARIABLE = "responseVariable";
+ private static final String RESPONSE_VALUE = "responseValue";
+ private static final String SCRIPT = "script";
+ private static final String TYPE = "type";
+ private static final String SQL = "sql";
+ private static final String COLUMN = "column";
+ private static final String VARIABLE = "variable";
+ // new
+ private static final String SCHEMA = "schema";
+ // new
+ private static final String SCHEMA_VALIDATION = "schemaValidation";
+
+ private final Class> beanClass;
+
+ public OpenApiFromWsdlBeanDefinitionParser(Class> beanClass) {
+ this.beanClass = beanClass;
+ }
+
+ public BeanDefinition parse(Element element) {
+ return parse(element, null);
+ }
+
+ /**
+ * Note: The {@link OpenApiFromWsdlBeanDefinitionParser#parse(Element element)} allows access direct
+ * access without the {@link org.springframework.beans.factory.xml.ParserContext} for convenience.
+ */
+ @Override
+ public BeanDefinition parse(Element element, ParserContext parserContext) {
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClass);
+ retrieveRootNodeAttributes(element, builder);
+ retrieveOptionalNodeAttributes(element, REQUEST_BODY, builder);
+ retrieveTextContentAndNodeAttributes(element, REQUEST_BODY_LITERAL, builder);
+ retrieveOptionalNodeAttributes(element, RESPONSE, builder);
+ retrieveParamNodeData(element, builder, COOKIE);
+ retrieveParamNodeData(element, builder, HEADER);
+ retrieveParamNodeData(element, builder, SOAP_HEADER);
+ retrieveParamNodeData(element, builder, MIME_HEADER);
+ retrieveOptionalNodeAttributes(element, SCHEMA, builder);
+ retrieveOptionalNodeAttributes(element, SCHEMA_VALIDATION, builder);
+ retrieveOptionalMultipartElements(element, builder);
+ retrieveResponseNodeData(element, builder);
+ builder.addPropertyValue("name", element.getTagName());
+ return builder.getBeanDefinition();
+ }
+
+ private void retrieveOptionalMultipartElements(Element element, BeanDefinitionBuilder builder) {
+ var multipartBodyElement = DomUtils.getChildElementByTagName(element, MULTIPART_BODY);
+ if (multipartBodyElement != null) {
+ var multipartBodyChildElements = DomUtils.getChildElements(multipartBodyElement);
+ for(int i = 0; i < multipartBodyChildElements.size(); i++){
+ var multipartBodyChildElement = multipartBodyChildElements.get(i);
+ String propertyName = Conventions.attributeNameToPropertyName(multipartBodyChildElement.getLocalName());
+ builder.addPropertyValue(propertyName, multipartBodyChildElement.getTextContent());
+ }
+ }
+ }
+
+ private void retrieveRootNodeAttributes(Element element, BeanDefinitionBuilder builder) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty.");
+ builder.addPropertyValue(propertyName, attribute.getValue());
+ }
+ }
+
+ private void retrieveOptionalNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) {
+ if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) {
+ Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ NamedNodeMap attributes = el.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty.");
+ String variableName = el.getLocalName() + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
+ builder.addPropertyValue(variableName, attribute.getValue());
+ }
+ }
+ }
+
+ private void retrieveTextContentAndNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) {
+ if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) {
+ Element el1 = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ NamedNodeMap attributes = el1.getAttributes();
+ for (int x = 0; x < attributes.getLength(); x++) {
+ Attr attribute = (Attr) attributes.item(x);
+ String propertyName1 = Conventions.attributeNameToPropertyName(attribute.getLocalName());
+ Assert.state(StringUtils.isNotBlank(propertyName1), "Illegal property name returned, it must not be null or empty.");
+ String variableName = el1.getLocalName() + propertyName1.substring(0, 1).toUpperCase() + propertyName1.substring(1);
+ builder.addPropertyValue(variableName, attribute.getValue());
+ }
+ Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0);
+ builder.addPropertyValue(elementName, el.getTextContent());
+ }
+ }
+
+ private void retrieveParamNodeData(Element element, BeanDefinitionBuilder builder, String paramType) {
+ if (!DomUtils.getChildElementsByTagName(element, paramType).isEmpty()) {
+ Map params = new HashMap<>();
+ List elements = DomUtils.getChildElementsByTagName(element, paramType);
+ elements.forEach(e -> {
+ String name = e.getAttribute(NAME);
+ String value = e.getAttribute(VALUE);
+
+ Assert.state(StringUtils.isNotBlank(name), "Illegal attribute value returned. The 'name' attribute must not be null or empty.");
+ Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty.");
+
+ params.put(name, value);
+ });
+ builder.addPropertyValue(paramType, params);
+ }
+ }
+
+ private void retrieveResponseNodeData(Element element, BeanDefinitionBuilder builder) {
+
+ if (!DomUtils.getChildElementsByTagName(element, RESPONSE).isEmpty()) {
+ Element response = DomUtils.getChildElementsByTagName(element, RESPONSE).get(0);
+ List elements = DomUtils.getChildElements(response);
+
+ Map responseVariable = new HashMap<>();
+ Map responseValue = new HashMap<>();
+
+ for (int i = 0; i < elements.size(); i++) {
+ Element e = elements.get(i);
+
+ if (e.getTagName().contains(RESPONSE_JSONPATH) || e.getTagName().contains(RESPONSE_XPATH)) {
+ String expression = e.getAttribute(EXPRESSION);
+ String value = e.getAttribute(VALUE);
+
+ Assert.state(StringUtils.isNotBlank(expression), "Illegal attribute value returned. The 'expression' attribute must not be null or empty.");
+ Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty.");
+
+ // variable to save @variable('ebid')@ else value to validate
+ if (value.matches("\\@variable\\('.*'\\)\\@")) {
+ Matcher match = Pattern.compile("\\'(.*?)\\'").matcher(value);
+ if (match.find()) {
+ responseVariable.put(expression, value.substring(match.start() + 1, match.end() - 1));
+ }
+ } else {
+ responseValue.put(expression, value);
+ }
+ } else if (e.getTagName().contains(SCRIPT)) {
+ String script = e.getTextContent();
+ Assert.state(StringUtils.isNotBlank(script), "Illegal attribute value returned. The 'script' attribute must not be null or empty.");
+ builder.addPropertyValue(SCRIPT, script);
+
+ if (!e.getAttribute(TYPE).isEmpty()) {
+ String type = e.getAttribute(TYPE);
+ Assert.state(StringUtils.isNotBlank(type), "Illegal attribute value returned. The 'type' attribute must not be null or empty.");
+ builder.addPropertyValue(TYPE, type);
+ }
+ } else if (e.getTagName().contains(RESPONSE_RESOURCE)) {
+ String filePath = e.getAttribute(FILE);
+ Assert.state(StringUtils.isNotBlank(filePath), "Illegal attribute value returned. The 'file' attribute must not be null or empty.");
+ builder.addPropertyValue(RESPONSE_RESOURCE, filePath);
+ }
+
+ }
+
+ builder.addPropertyValue(RESPONSE_VARIABLE, responseVariable);
+ builder.addPropertyValue(RESPONSE_VALUE, responseValue);
+ }
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/extension/OpenApiFromWsdlNamespaceHandler.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/extension/OpenApiFromWsdlNamespaceHandler.java
new file mode 100644
index 0000000000..04773ddb99
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/extension/OpenApiFromWsdlNamespaceHandler.java
@@ -0,0 +1,26 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.soap.bookservice.citrus.extension;
+
+import org.citrusframework.openapi.generator.soap.bookservice.request.BookServiceSoapApi;
+import org.citrusframework.openapi.generator.soap.bookservice.citrus.OpenApiFromWsdlBeanDefinitionParser;
+
+import javax.annotation.processing.Generated;
+
+import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
+
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class OpenApiFromWsdlNamespaceHandler extends NamespaceHandlerSupport {
+
+ @Override
+ public void init() {
+ registerBeanDefinitionParser("addBookRequest", new OpenApiFromWsdlBeanDefinitionParser(BookServiceSoapApi.AddBookRequest.class));
+ registerBeanDefinitionParser("getAllBooksRequest", new OpenApiFromWsdlBeanDefinitionParser(BookServiceSoapApi.GetAllBooksRequest.class));
+ registerBeanDefinitionParser("getBookRequest", new OpenApiFromWsdlBeanDefinitionParser(BookServiceSoapApi.GetBookRequest.class));
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/request/BookServiceSoapApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/request/BookServiceSoapApi.java
new file mode 100644
index 0000000000..dd84878034
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/request/BookServiceSoapApi.java
@@ -0,0 +1,330 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.soap.bookservice.request;
+
+import jakarta.annotation.Generated;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.citrusframework.context.TestContext;
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.citrusframework.testapi.GeneratedApi;
+import org.citrusframework.testapi.GeneratedApiRequest;
+import org.citrusframework.openapi.generator.soap.bookservice.citrus.OpenApiFromWsdlAbstractTestRequest;
+import org.citrusframework.spi.Resources;
+import org.citrusframework.util.FileUtils;
+import org.citrusframework.ws.actions.SendSoapMessageAction;
+import org.citrusframework.ws.actions.SendSoapMessageAction.Builder.SendSoapMessageBuilderSupport;
+import org.citrusframework.ws.actions.SoapActionBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.CollectionUtils;
+
+import org.citrusframework.openapi.generator.soap.bookservice.citrus.OpenApiFromWsdlAbstractTestRequest;
+
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class BookServiceSoapApi implements GeneratedApi
+{
+ public static final BookServiceSoapApi INSTANCE = new BookServiceSoapApi();
+
+ public String getApiTitle() {
+ return "Generated api from wsdl";
+ }
+
+ public String getApiVersion() {
+ return "1.0.0";
+ }
+
+ public String getApiPrefix() {
+ return "OpenApiFromWsdl";
+ }
+
+ public Map getApiInfoExtensions() {
+ Map infoExtensionMap = new HashMap<>();
+ return infoExtensionMap;
+ }
+
+ /**
+ addBook (POST /AddBook)
+
+
+ **/
+ public static class AddBookRequest extends OpenApiFromWsdlAbstractTestRequest implements GeneratedApiRequest {
+
+ private final Logger coverageLogger = LoggerFactory.getLogger(AddBookRequest.class);
+
+ // Query params
+
+
+ public AddBookRequest(){
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("OpenApiFromWsdl".toLowerCase() + ":addBookRequestType");
+ }
+
+ public String getOperationName() {
+ return "addBook";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/AddBook";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+
+ SendSoapMessageAction.Builder soapSendMessageActionBuilder = new SoapActionBuilder().client(wsClient).send();
+ SendSoapMessageBuilderSupport messageBuilderSupport = soapSendMessageActionBuilder.getMessageBuilderSupport();
+
+ messageBuilderSupport.soapAction("addBook");
+
+ String payload = null;
+ String payloadType = null;
+
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ if (!CollectionUtils.isEmpty(soapHeaders)) {
+ for (Entry entry : soapHeaders.entrySet()) {
+ messageBuilderSupport = messageBuilderSupport.header(entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ if (!CollectionUtils.isEmpty(mimeHeaders)) {
+ for (Entry entry : mimeHeaders.entrySet()) {
+ messageBuilderSupport = messageBuilderSupport.header("citrus_http_" + entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ soapSendMessageActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ soapSendMessageActionBuilder = customizeBuilder(INSTANCE, context, soapSendMessageActionBuilder);
+
+ soapSendMessageActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "addBook;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"");
+ }
+
+
+ }
+ /**
+ getAllBooks (POST /GetAllBooks)
+
+
+ **/
+ public static class GetAllBooksRequest extends OpenApiFromWsdlAbstractTestRequest implements GeneratedApiRequest {
+
+ private final Logger coverageLogger = LoggerFactory.getLogger(GetAllBooksRequest.class);
+
+ // Query params
+
+
+ public GetAllBooksRequest(){
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("OpenApiFromWsdl".toLowerCase() + ":getAllBooksRequestType");
+ }
+
+ public String getOperationName() {
+ return "getAllBooks";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/GetAllBooks";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+
+ SendSoapMessageAction.Builder soapSendMessageActionBuilder = new SoapActionBuilder().client(wsClient).send();
+ SendSoapMessageBuilderSupport messageBuilderSupport = soapSendMessageActionBuilder.getMessageBuilderSupport();
+
+ messageBuilderSupport.soapAction("getAllBooks");
+
+ String payload = null;
+ String payloadType = null;
+
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ if (!CollectionUtils.isEmpty(soapHeaders)) {
+ for (Entry entry : soapHeaders.entrySet()) {
+ messageBuilderSupport = messageBuilderSupport.header(entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ if (!CollectionUtils.isEmpty(mimeHeaders)) {
+ for (Entry entry : mimeHeaders.entrySet()) {
+ messageBuilderSupport = messageBuilderSupport.header("citrus_http_" + entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ soapSendMessageActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ soapSendMessageActionBuilder = customizeBuilder(INSTANCE, context, soapSendMessageActionBuilder);
+
+ soapSendMessageActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "getAllBooks;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"");
+ }
+
+
+ }
+ /**
+ getBook (POST /GetBook)
+
+
+ **/
+ public static class GetBookRequest extends OpenApiFromWsdlAbstractTestRequest implements GeneratedApiRequest {
+
+ private final Logger coverageLogger = LoggerFactory.getLogger(GetBookRequest.class);
+
+ // Query params
+
+
+ public GetBookRequest(){
+ // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
+ setName("OpenApiFromWsdl".toLowerCase() + ":getBookRequestType");
+ }
+
+ public String getOperationName() {
+ return "getBook";
+ }
+
+ public String getMethod() {
+ return "POST";
+ }
+
+ public String getPath() {
+ return "/GetBook";
+ }
+
+ /**
+ * This method sends the HTTP-Request
+ */
+ public void sendRequest(TestContext context) {
+
+ SendSoapMessageAction.Builder soapSendMessageActionBuilder = new SoapActionBuilder().client(wsClient).send();
+ SendSoapMessageBuilderSupport messageBuilderSupport = soapSendMessageActionBuilder.getMessageBuilderSupport();
+
+ messageBuilderSupport.soapAction("getBook");
+
+ String payload = null;
+ String payloadType = null;
+
+ if (StringUtils.isNotBlank(this.bodyFile)) {
+ try {
+ payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Failed to read payload resource", e);
+ }
+ payloadType = this.bodyContentType;
+ } else if (StringUtils.isNotBlank(this.bodyLiteral)) {
+ payload = this.bodyLiteral;
+ payloadType = this.bodyLiteralContentType;
+ }
+
+ String body = "";
+ String bodyType = "";
+ if(payload != null && payloadType != null) {
+ messageBuilderSupport.body(payload).contentType(payloadType);
+ body = context.replaceDynamicContentInString(payload);
+ bodyType = context.replaceDynamicContentInString(payloadType);
+ }
+
+ if (!CollectionUtils.isEmpty(soapHeaders)) {
+ for (Entry entry : soapHeaders.entrySet()) {
+ messageBuilderSupport = messageBuilderSupport.header(entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ if (!CollectionUtils.isEmpty(mimeHeaders)) {
+ for (Entry entry : mimeHeaders.entrySet()) {
+ messageBuilderSupport = messageBuilderSupport.header("citrus_http_" + entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ Map queryParams = new HashMap<>();
+
+ String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
+
+ soapSendMessageActionBuilder.withReferenceResolver(context.getReferenceResolver());
+ soapSendMessageActionBuilder = customizeBuilder(INSTANCE, context, soapSendMessageActionBuilder);
+
+ soapSendMessageActionBuilder.build().execute(context);
+
+ coverageLogger.trace(coverageMarker, "getBook;POST;\"" +
+ query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
+ body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"");
+ }
+
+
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/spring/OpenApiFromWsdlBeanConfiguration.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/spring/OpenApiFromWsdlBeanConfiguration.java
new file mode 100644
index 0000000000..e462cae9f8
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/spring/OpenApiFromWsdlBeanConfiguration.java
@@ -0,0 +1,38 @@
+/**
+ * ==================================================
+ * GENERATED CLASS, ALL CHANGES WILL BE LOST
+ * ==================================================
+ */
+
+package org.citrusframework.openapi.generator.soap.bookservice.spring;
+
+import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
+
+import org.citrusframework.openapi.generator.soap.bookservice.request.BookServiceSoapApi;
+import javax.annotation.processing.Generated;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Scope;
+
+@Configuration
+@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen")
+public class OpenApiFromWsdlBeanConfiguration {
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public BookServiceSoapApi.AddBookRequest addBookRequest() {
+ return new BookServiceSoapApi.AddBookRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public BookServiceSoapApi.GetAllBooksRequest getAllBooksRequest() {
+ return new BookServiceSoapApi.GetAllBooksRequest();
+ }
+
+ @Bean
+ @Scope(SCOPE_PROTOTYPE)
+ public BookServiceSoapApi.GetBookRequest getBookRequest() {
+ return new BookServiceSoapApi.GetBookRequest();
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookDatatypes.xsd b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookDatatypes.xsd
new file mode 100644
index 0000000000..c093acef11
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookDatatypes.xsd
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService-generated.yaml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService-generated.yaml
new file mode 100644
index 0000000000..480e462dfe
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService-generated.yaml
@@ -0,0 +1,33 @@
+---
+info:
+ contact:
+ name: "org.citrusframework.openapi.generator.SimpleWsdlToOpenApiTransformer"
+ description: "This api has been generated from the following wsdl 'BookService.wsdl'.\
+ \ It's purpose is solely to serve as input for SOAP API generation. Note that\
+ \ only operations are extracted from the WSDL. No schema information whatsoever\
+ \ is generated!"
+ title: "Generated api from wsdl"
+ version: "1.0.0"
+openapi: "3.0.1"
+paths:
+ /GetBook:
+ post:
+ description: ""
+ operationId: "GetBook"
+ responses: {}
+ tags:
+ - "BookServiceSOAP"
+ /AddBook:
+ post:
+ description: ""
+ operationId: "AddBook"
+ responses: {}
+ tags:
+ - "BookServiceSOAP"
+ /GetAllBooks:
+ post:
+ description: ""
+ operationId: "GetAllBooks"
+ responses: {}
+ tags:
+ - "BookServiceSOAP"
\ No newline at end of file
diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService.wsdl b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService.wsdl
new file mode 100644
index 0000000000..5243c102d5
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService.wsdl
@@ -0,0 +1,110 @@
+
+
+ Definition for a web service called BookService,
+ which can be used to add or retrieve books from a collection.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/pom.xml b/test-api-generator/citrus-test-api-generator-maven-plugin/pom.xml
new file mode 100644
index 0000000000..e92fd84d94
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-maven-plugin/pom.xml
@@ -0,0 +1,111 @@
+
+ 4.0.0
+
+
+ citrus-test-api-generator
+ org.citrusframework
+ 4.3.0-SNAPSHOT
+ ../pom.xml
+
+
+ citrus-test-api-generator-maven-plugin
+ maven-plugin
+
+ Citrus :: Test API Generator :: Maven Plugin
+ Maven Plugin for generation of Citrus Test API
+
+
+ 2.2.21
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ ${maven.plugin.plugin.version}
+
+ true
+
+
+
+
+
+
+
+
+ org.citrusframework
+ citrus-test-api-generator-core
+ ${project.version}
+
+
+
+ commons-io
+ commons-io
+ ${commons.io.version}
+
+
+ io.swagger.core.v3
+ swagger-core
+ ${swagger.version}
+
+
+ io.swagger.core.v3
+ swagger-models-jakarta
+ ${swagger.version}
+
+
+ org.apache.maven
+ maven-artifact
+ ${maven.version}
+ provided
+
+
+ org.apache.maven
+ maven-core
+ ${maven.version}
+ provided
+
+
+ org.apache.maven
+ maven-plugin-api
+ ${maven.version}
+ provided
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ ${maven.plugin.annotations.version}
+ provided
+
+
+
+
+ org.apache.maven
+ maven-compat
+ ${maven.version}
+ test
+
+
+ org.apache.maven.plugin-testing
+ maven-plugin-testing-harness
+ ${maven.plugin.testing.harness.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+
diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/CodeGenMojoWrapper.java b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/CodeGenMojoWrapper.java
new file mode 100644
index 0000000000..ea92db1efb
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/CodeGenMojoWrapper.java
@@ -0,0 +1,86 @@
+package org.citrusframework.maven.plugin;
+
+import static org.citrusframework.openapi.generator.JavaCitrusCodegen.CODEGEN_NAME;
+import static java.lang.String.format;
+
+import org.citrusframework.openapi.generator.JavaCitrusCodegen;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.project.MavenProject;
+import org.openapitools.codegen.plugin.CodeGenMojo;
+
+/**
+ * Wrapper class that uses reflection to expose several properties of the {@link CodeGenMojo} for explicit assignment.
+ *
+ * @author Thorsten Schlathoelter
+ */
+public class CodeGenMojoWrapper extends CodeGenMojo {
+
+ private final Map configOptionsProperties = new HashMap<>();
+
+ public CodeGenMojoWrapper() throws MojoExecutionException {
+ setFixedConfigOptions();
+ setPrivateField("configOptions", configOptionsProperties);
+ }
+
+ private void setFixedConfigOptions() throws MojoExecutionException {
+ setPrivateField("generateSupportingFiles", true);
+ setPrivateField( "generatorName", CODEGEN_NAME);
+ }
+
+ public CodeGenMojoWrapper project(MavenProject mavenProject) throws MojoExecutionException {
+ setPrivateField("project", mavenProject);
+ return this;
+ }
+
+ public CodeGenMojoWrapper output(File output) throws MojoExecutionException {
+ setPrivateField("output", output);
+ return this;
+ }
+
+ public CodeGenMojoWrapper inputSpec(String inputSpec) throws MojoExecutionException {
+ setPrivateField("inputSpec", inputSpec);
+ return this;
+ }
+
+ public CodeGenMojoWrapper mojoExecution(MojoExecution mojoExecution) throws MojoExecutionException {
+ setPrivateField("mojo", mojoExecution);
+ return this;
+ }
+
+ public CodeGenMojoWrapper configOptions(Map configOptionsProperties) {
+ this.configOptionsProperties.putAll(configOptionsProperties);
+ return this;
+ }
+
+ public CodeGenMojoWrapper schemaFolder(String schemaFolder) {
+ configOptionsProperties.put(JavaCitrusCodegen.GENERATED_SCHEMA_FOLDER, schemaFolder);
+ return this;
+ }
+
+ public CodeGenMojoWrapper resourceFolder(String resourceFolder) {
+ configOptionsProperties.put(JavaCitrusCodegen.RESOURCE_FOLDER, resourceFolder);
+ return this;
+ }
+
+ public CodeGenMojoWrapper sourceFolder(String sourceFolder) {
+ configOptionsProperties.put(JavaCitrusCodegen.SOURCE_FOLDER, sourceFolder);
+ return this;
+ }
+
+ @SuppressWarnings("java:S3011") // Accessibility bypass
+ private void setPrivateField(String fieldName, Object fieldValue) throws MojoExecutionException {
+ try {
+ var field = CodeGenMojo.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(this, fieldValue);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new MojoExecutionException(
+ format("Could not reflectively set field value '%s' for field '%s'", fieldValue, fieldName));
+ }
+ }
+
+}
diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/SpringMetaFileGenerator.java b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/SpringMetaFileGenerator.java
new file mode 100644
index 0000000000..23ae7b161f
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/SpringMetaFileGenerator.java
@@ -0,0 +1,156 @@
+package org.citrusframework.maven.plugin;
+
+import static java.lang.String.format;
+import static java.util.Collections.emptyList;
+
+import org.citrusframework.maven.plugin.TestApiGeneratorMojo.ApiConfig;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.citrusframework.exceptions.CitrusRuntimeException;
+
+/**
+ * Utility class responsible for generating the Spring meta files 'spring.handlers' and 'spring.schemas', used
+ * in Spring integration testing. These meta files define mappings between XML namespace URIs and corresponding
+ * handler classes. The class provides methods to generate these meta files based on the configuration provided.
+ *
+ * The generated meta files can be created either in the generated folder or in the main resources folder. See
+ * {@link TestApiGeneratorMojo#RESOURCE_FOLDER_PROPERTY} for details. The implemented algorithm carefully updates these
+ * files and tries to keep non generated information unchanged. Therefore, a special segment in the namespace uri is used, namely
+ * {@link TestApiGeneratorMojo#CITRUS_TEST_SCHEMA}.
+ *
+ *
+ * @author Thorsten Schlathoelter
+ *
+ */
+public class SpringMetaFileGenerator {
+
+ private final TestApiGeneratorMojo testApiGeneratorMojo;
+
+ public SpringMetaFileGenerator(TestApiGeneratorMojo testApiGeneratorMojo) {
+ this.testApiGeneratorMojo = testApiGeneratorMojo;
+ }
+
+ public void generateSpringIntegrationMetaFiles() throws MojoExecutionException {
+
+ String springMetafileDirectory = format("%s/%s", testApiGeneratorMojo.getMavenProject().getBasedir(),
+ testApiGeneratorMojo.metaInfFolder());
+ File metaFolder = new File(springMetafileDirectory);
+ if (!metaFolder.exists() && !metaFolder.mkdirs()) {
+ throw new CitrusRuntimeException(
+ format("Unable to create spring meta file directory: '%s'", springMetafileDirectory));
+ }
+
+ try {
+ writeSpringSchemaMetaFile(metaFolder);
+ writeSpringHandlerMetaFile(metaFolder);
+ } catch (MetaFileWriteException e) {
+ throw new MojoExecutionException(e);
+ }
+ }
+
+ private void writeSpringSchemaMetaFile(File springMetafileDirectory) throws MojoExecutionException {
+
+ String filename = "spring.schemas";
+ writeSpringMetaFile(springMetafileDirectory, filename, (fileWriter, apiConfig) -> {
+ String targetXmlnsNamespace = TestApiGeneratorMojo.replaceDynamicVarsToLowerCase(apiConfig.getTargetXmlnsNamespace(), apiConfig.getPrefix(),
+ apiConfig.getVersion());
+ String schemaFolderPath = TestApiGeneratorMojo.replaceDynamicVars(testApiGeneratorMojo.schemaFolder(apiConfig), apiConfig.getPrefix(),
+ apiConfig.getVersion());
+ String schemaPath = String.format("%s/%s-api.xsd", schemaFolderPath, apiConfig.getPrefix().toLowerCase());
+ appendLine(fileWriter, format("%s.xsd=%s%n", targetXmlnsNamespace.replace("http://", "http\\://"), schemaPath), filename);
+ });
+ }
+
+ private void writeSpringHandlerMetaFile(File springMetafileDirectory) throws MojoExecutionException {
+ String filename = "spring.handlers";
+ writeSpringMetaFile(springMetafileDirectory, filename, (fileWriter, apiConfig) -> {
+ String targetXmlnsNamespace = TestApiGeneratorMojo.replaceDynamicVarsToLowerCase(apiConfig.getTargetXmlnsNamespace(), apiConfig.getPrefix(),
+ apiConfig.getVersion());
+ String invokerPackage = TestApiGeneratorMojo.replaceDynamicVarsToLowerCase(apiConfig.getInvokerPackage(), apiConfig.getPrefix(), apiConfig.getVersion());
+ String namespaceHandlerClass = invokerPackage + ".citrus.extension." + apiConfig.getPrefix() + "NamespaceHandler";
+ appendLine(fileWriter, format("%s=%s%n", targetXmlnsNamespace.replace("http://", "http\\://"), namespaceHandlerClass),
+ filename);
+ });
+ }
+
+ private void writeSpringMetaFile(File springMetafileDirectory, String filename, BiConsumer contentFormatter)
+ throws MojoExecutionException {
+
+ File handlerFile = new File(format("%s/%s", springMetafileDirectory.getPath(), filename));
+ List filteredLines = readAndFilterLines(handlerFile);
+
+ try (FileWriter fileWriter = new FileWriter(handlerFile)) {
+
+ for (String line : filteredLines) {
+ fileWriter.write(format("%s%n", line));
+ }
+
+ for (ApiConfig apiConfig : testApiGeneratorMojo.getApiConfigs()) {
+ contentFormatter.accept(fileWriter, apiConfig);
+ }
+
+ } catch (IOException e) {
+ throw new MojoExecutionException("Unable to write spring meta file!", e);
+ }
+ }
+
+ /**
+ * Reads the lines from the specified file and filters out lines indicating a generated test API,
+ * while maintaining all non-generated test API lines. This method is used to process files
+ * containing both generated and non-generated test APIs, allowing seamless integration and
+ * modification of both types of APIs in the same source files.
+ *
+ *
+ * Generated test API lines are identified by the presence of the {@code CITRUS_TEST_SCHEMA}
+ * string and excluded from the output of this method, while all other lines are preserved.
+ * This enables the algorithm to operate on files that are not purely generated, for example,
+ * when mixing generated with non-generated APIs in 'src/main/META-INF'.
+ *
+ *
+ * @param file the file to read and filter
+ * @return a list of filtered lines, excluding lines indicating a generated test API
+ * @throws CitrusRuntimeException if an error occurs while reading the file
+ */
+ private static List readAndFilterLines(File file) {
+
+ if (!file.exists()) {
+ return emptyList();
+ }
+
+ List filteredLines = new ArrayList<>();
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (!line.contains(TestApiGeneratorMojo.CITRUS_TEST_SCHEMA)) {
+ filteredLines.add(line);
+ }
+ }
+ } catch (IOException e) {
+ throw new CitrusRuntimeException(format("Unable to read file file: '%s'", file.getPath()), e);
+ }
+
+ return filteredLines;
+ }
+
+ private void appendLine(FileWriter fileWriter, String format, String filename) {
+ try {
+ fileWriter.append(format);
+ } catch (IOException e) {
+ throw new MetaFileWriteException(format("Unable to write spring meta file '%s'!", filename), e);
+ }
+ }
+
+ private static final class MetaFileWriteException extends RuntimeException {
+
+ public MetaFileWriteException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/TestApiGeneratorMojo.java b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/TestApiGeneratorMojo.java
new file mode 100644
index 0000000000..d5b2b1cd53
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/TestApiGeneratorMojo.java
@@ -0,0 +1,412 @@
+package org.citrusframework.maven.plugin;
+
+import static org.citrusframework.openapi.generator.JavaCitrusCodegen.API_ENDPOINT;
+import static org.citrusframework.openapi.generator.JavaCitrusCodegen.API_TYPE;
+import static org.citrusframework.openapi.generator.JavaCitrusCodegen.PREFIX;
+import static org.citrusframework.openapi.generator.JavaCitrusCodegen.TARGET_XMLNS_NAMESPACE;
+import static java.lang.String.format;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.project.MavenProject;
+import org.openapitools.codegen.plugin.CodeGenMojo;
+import org.sonatype.plexus.build.incremental.BuildContext;
+import org.sonatype.plexus.build.incremental.DefaultBuildContext;
+
+/**
+ * The Citrus OpenAPI Generator Maven Plugin is designed to facilitate the integration of multiple OpenAPI specifications
+ * into the Citrus testing framework by automatically generating necessary test classes and XSDs. This plugin wraps the
+ * {@code CodeGenMojo} and extends its functionality to support multiple API configurations.
+ *
+ * Features:
+ * - Multiple API Configurations: Easily configure multiple OpenAPI specifications to generate test APIs with specific prefixes.
+ * - Citrus Integration: Generates classes and XSDs tailored for use within the Citrus framework, streamlining the process
+ * of creating robust integration tests.
+ *
+ *
+ * @author Thorsten Schlathoelter
+ *
+ */
+@Mojo(
+ name = "create-test-api",
+ defaultPhase = LifecyclePhase.GENERATE_TEST_SOURCES,
+ requiresDependencyCollection = ResolutionScope.TEST,
+ requiresDependencyResolution = ResolutionScope.TEST,
+ threadSafe = true
+)
+public class TestApiGeneratorMojo extends AbstractMojo {
+
+ public static final String DEFAULT_SOURCE_FOLDER = "generated-test-sources";
+ public static final String DEFAULT_RESOURCE_FOLDER = "generated-test-resources";
+ public static final String DEFAULT_BASE_PACKAGE = "org.citrusframework.automation.%PREFIX%.%VERSION%";
+ public static final String DEFAULT_INVOKER_PACKAGE = DEFAULT_BASE_PACKAGE;
+ public static final String DEFAULT_API_PACKAGE = DEFAULT_BASE_PACKAGE+".api";
+ public static final String DEFAULT_MODEL_PACKAGE = DEFAULT_BASE_PACKAGE+".model";
+ public static final String DEFAULT_SCHEMA_FOLDER_TEMPLATE = "schema/xsd/%VERSION%";
+ public static final ApiType DEFAULT_API_TYPE = ApiType.REST;
+
+ /**
+ * Marker fragment in the schema name of a generated schema. Used to distinguish generated from non generated values, when manipulating
+ * spring meta-data files.
+ */
+ public static final String CITRUS_TEST_SCHEMA = "citrus-test-schema";
+
+ /**
+ * Specifies the default target namespace template. When changing the default value, it's important to maintain the 'citrus-test-schema'
+ * part, as this name serves to differentiate between generated and non-generated schemas. This differentiation aids in the creation of
+ * supporting Spring files such as 'spring.handlers' and 'spring.schemas'.
+ */
+ public static final String DEFAULT_TARGET_NAMESPACE_TEMPLATE =
+ "http://www.citrusframework.org/" + CITRUS_TEST_SCHEMA + "/%VERSION%/%PREFIX%-api";
+
+ /**
+ * The default META-INF folder. Note that it points into the main resources, not generated resources, to allow for non generated
+ * schemas/handlers. See also {@link TestApiGeneratorMojo}#DEFAULT_TARGET_NAMESPACE_TEMPLATE.
+ */
+ public static final String DEFAULT_META_INF_FOLDER = "target/generated-test-resources/META-INF";
+
+ @Component
+ private final BuildContext buildContext = new DefaultBuildContext();
+
+ @Parameter(defaultValue = "${project}", readonly = true)
+ private MavenProject mavenProject;
+
+ @Parameter(defaultValue = "${mojoExecution}", readonly = true)
+ private MojoExecution mojoExecution;
+
+ /**
+ * sourceFolder: specifies the location to which the sources are generated. Defaults to 'generated-test-sources'.
+ */
+ public static final String SOURCE_FOLDER_PROPERTY = "citrus.test.api.generator.source.folder";
+ @Parameter(property = SOURCE_FOLDER_PROPERTY, defaultValue = DEFAULT_SOURCE_FOLDER)
+ @SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"}) // Maven injected
+ private String sourceFolder = DEFAULT_SOURCE_FOLDER;
+
+ /**
+ * resourceFolder: specifies the location to which the resources are generated. Defaults to 'generated-test-resources'.
+ */
+ public static final String RESOURCE_FOLDER_PROPERTY = "citrus.test.api.generator.resource.folder";
+ @Parameter(property = RESOURCE_FOLDER_PROPERTY, defaultValue = DEFAULT_RESOURCE_FOLDER)
+ @SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"}) // Maven injected
+ private String resourceFolder = DEFAULT_RESOURCE_FOLDER;
+
+ /**
+ * schemaFolder: specifies the location for the generated xsd schemas. Defaults to 'schema/xsd/%VERSION%'
+ */
+ public static final String API_SCHEMA_FOLDER = "citrus.test.api.generator.schema.folder";
+ @Parameter(property = API_SCHEMA_FOLDER, defaultValue = DEFAULT_SCHEMA_FOLDER_TEMPLATE)
+ @SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"}) // Maven injected
+ private String schemaFolder = DEFAULT_SCHEMA_FOLDER_TEMPLATE;
+
+ /**
+ * metaInfFolder: specifies the location to which the resources are generated. Defaults to 'generated-resources'.
+ */
+ public static final String META_INF_FOLDER = "citrus.test.api.generator.meta.inf.folder";
+ @Parameter(property = META_INF_FOLDER, defaultValue = DEFAULT_META_INF_FOLDER)
+ @SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"}) // Maven injected
+ private String metaInfFolder = DEFAULT_META_INF_FOLDER;
+
+ /**
+ * resourceFolder: specifies the location to which the resources are generated. Defaults to 'generated-resources'.
+ */
+ public static final String GENERATE_SPRING_INTEGRATION_FILES = "citrus.test.api.generator.generate.spring.integration.files";
+ @Parameter(property = GENERATE_SPRING_INTEGRATION_FILES, defaultValue = "true")
+ @SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"}) // Maven injected
+ private boolean generateSpringIntegrationFiles = true;
+
+ @Parameter
+ private List apis;
+
+ protected MavenProject getMavenProject() {
+ return mavenProject;
+ }
+
+ protected void setMavenProject(MavenProject mavenProject) {
+ this.mavenProject = mavenProject;
+ }
+
+ public List getApiConfigs() {
+ return apis;
+ }
+
+ public String metaInfFolder() {
+ return metaInfFolder;
+ }
+
+ @VisibleForTesting
+ void setMojoExecution(MojoExecution mojoExecution) {
+ this.mojoExecution = mojoExecution;
+ }
+
+ /**
+ * Returns the fully qualified schema folder
+ */
+ public String schemaFolder(ApiConfig apiConfig) {
+ return replaceDynamicVars(schemaFolder, apiConfig.getPrefix(), apiConfig.getVersion());
+ }
+
+ @Override
+ public void execute() throws MojoExecutionException {
+
+ for (int index = 0; index < apis.size(); index++) {
+ ApiConfig apiConfig = apis.get(index);
+ validateApiConfig(index, apiConfig);
+ CodeGenMojo codeGenMojo = configureCodeGenMojo(apiConfig);
+ codeGenMojo.execute();
+ }
+
+ if (generateSpringIntegrationFiles) {
+ new SpringMetaFileGenerator(this).generateSpringIntegrationMetaFiles();
+ }
+ }
+
+ CodeGenMojo configureCodeGenMojo(ApiConfig apiConfig) throws MojoExecutionException {
+ CodeGenMojo codeGenMojo = new CodeGenMojoWrapper()
+ .resourceFolder(resourceFolder)
+ .sourceFolder(sourceFolder)
+ .schemaFolder(schemaFolder(apiConfig))
+ .output(new File(mavenProject.getBuild().getDirectory()))
+ .mojoExecution(mojoExecution)
+ .project(mavenProject)
+ .inputSpec(apiConfig.getSource())
+ .configOptions(apiConfig.toConfigOptionsProperties());
+
+ codeGenMojo.setPluginContext(getPluginContext());
+ codeGenMojo.setBuildContext(buildContext);
+ return codeGenMojo;
+ }
+
+ private void validateApiConfig(int apiIndex, ApiConfig apiConfig) throws MojoExecutionException {
+ requireNonBlankParameter("prefix", apiIndex, apiConfig.getPrefix());
+ requireNonBlankParameter("source", apiIndex, apiConfig.getSource());
+ }
+
+ private void requireNonBlankParameter(String name, int index, String parameterValue) throws MojoExecutionException {
+ if (isBlank(parameterValue)) {
+ throw new MojoExecutionException(format("Required parameter '%s' not set for api at index '%d'!", name, index));
+ }
+ }
+
+ /**
+ * Replace the placeholders '%PREFIX%' and '%VERSION%' in the given text.
+ */
+ static String replaceDynamicVars(String text, String prefix, String version) {
+
+ if (text == null) {
+ return null;
+ }
+
+ return text.replace("%PREFIX%", prefix)
+ .replace(".%VERSION%", version != null ? "." + version : "")
+ .replace("/%VERSION%", version != null ? "/" + version : "")
+ .replace("-%VERSION%", version != null ? "-" + version : "")
+ .replace("%VERSION%", version != null ? version : "");
+ }
+
+ /**
+ * Replace the placeholders '%PREFIX%' and '%VERSION%' in the given text, performing a toLowerCase on the prefix.
+ */
+ static String replaceDynamicVarsToLowerCase(String text, String prefix, String version) {
+ return replaceDynamicVars(text, prefix.toLowerCase(), version);
+ }
+
+ public enum ApiType {
+ REST, SOAP
+ }
+
+ /**
+ * Note that the default values are not properly set by maven processor. Therefore, the default values have been assigned additionally
+ * on field level.
+ */
+ public static class ApiConfig {
+
+ public static final String DEFAULT_ENDPOINT = "PREFIX_ENDPOINT";
+
+ /**
+ * prefix: specifies the prefixed used for the test api. Typically, an acronym for the application which is being tested.
+ */
+ public static final String API_PREFIX_PROPERTY = "citrus.test.api.generator.prefix";
+ @Parameter(required = true, property = API_PREFIX_PROPERTY)
+ private String prefix;
+
+ /**
+ * source: specifies the source of the test api.
+ */
+ public static final String API_SOURCE_PROPERTY = "citrus.test.api.generator.source";
+ @Parameter(required = true, property = API_SOURCE_PROPERTY)
+ private String source;
+
+ /**
+ * version: specifies the version of the api. May be null.
+ */
+ public static final String API_VERSION_PROPERTY = "citrus.test.api.generator.version";
+ @Parameter(property = API_VERSION_PROPERTY)
+ private String version;
+
+ /**
+ * endpoint: specifies the endpoint of the test api. Defaults to 'prefixEndpoint'.
+ */
+ public static final String API_ENDPOINT_PROPERTY = "citrus.test.api.generator.endpoint";
+ @Parameter(property = API_ENDPOINT_PROPERTY, defaultValue = DEFAULT_ENDPOINT)
+ private String endpoint = DEFAULT_ENDPOINT;
+
+ /**
+ * type: specifies the type of the test api. Defaults to 'REST'
+ */
+ public static final String API_TYPE_PROPERTY = "citrus.test.api.generator.type";
+ @Parameter(property = API_TYPE_PROPERTY, defaultValue = "REST")
+ private ApiType type = DEFAULT_API_TYPE;
+
+ /**
+ * useTags: specifies whether tags should be used by the generator. Defaults to 'true'. If useTags is set to true, the generator
+ * will organize the generated code based on the tags defined in your API specification.
+ */
+ public static final String API_USE_TAGS_PROPERTY = "citrus.test.api.generator.use.tags";
+ @Parameter(property = API_USE_TAGS_PROPERTY, defaultValue = "true")
+ private boolean useTags = true;
+
+ /**
+ * invokerPackage: specifies the package for the test api classes. Defaults to
+ * 'org.citrusframework.automation.%PREFIX%.%VERSION%'.
+ */
+ public static final String API_INVOKER_PACKAGE_PROPERTY = "citrus.test.api.generator.invoker.package";
+ @Parameter(property = API_INVOKER_PACKAGE_PROPERTY, defaultValue = DEFAULT_INVOKER_PACKAGE)
+ private String invokerPackage = DEFAULT_INVOKER_PACKAGE;
+
+ /**
+ * apiPackage: specifies the package for the test api classes. Defaults to
+ * 'org.citrusframework.automation.%PREFIX%.%VERSION%.api'.
+ */
+ public static final String API_API_PACKAGE_PROPERTY = "citrus.test.api.generator.api.package";
+ @Parameter(property = API_API_PACKAGE_PROPERTY, defaultValue = DEFAULT_API_PACKAGE)
+ private String apiPackage = DEFAULT_API_PACKAGE;
+
+ /**
+ * modelPackage: specifies the package for the test api classes. Defaults to
+ * 'org.citrusframework.automation.%PREFIX%.%VERSION%.model'.
+ */
+ public static final String API_MODEL_PACKAGE_PROPERTY = "citrus.test.api.generator.model.package";
+ @Parameter(property = API_MODEL_PACKAGE_PROPERTY, defaultValue = DEFAULT_MODEL_PACKAGE)
+ private String modelPackage = DEFAULT_MODEL_PACKAGE;
+
+ /**
+ * targetXmlNamespace: specifies the xml namespace to be used by the api. Defaults to
+ * 'http://www.citrusframework.org/schema/%VERSION%/%PREFIX%-api'
+ */
+ @SuppressWarnings("JavadocLinkAsPlainText")
+ public static final String API_NAMESPACE_PROPERTY = "citrus.test.api.generator.namespace";
+ @Parameter(property = API_NAMESPACE_PROPERTY, defaultValue = DEFAULT_TARGET_NAMESPACE_TEMPLATE)
+ private String targetXmlnsNamespace = DEFAULT_TARGET_NAMESPACE_TEMPLATE;
+
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public void setSource(String source) {
+ this.source = source;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String qualifiedEndpoint() {
+ return DEFAULT_ENDPOINT.equals(endpoint) ? getPrefix().toLowerCase() + "Endpoint" : endpoint;
+ }
+
+ public void setEndpoint(String endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ public ApiType getType() {
+ return type;
+ }
+
+ public void setType(ApiType type) {
+ this.type = type;
+ }
+
+ public void setUseTags(boolean useTags) {
+ this.useTags = useTags;
+ }
+
+ public String getInvokerPackage() {
+ return invokerPackage;
+ }
+
+ public void setInvokerPackage(String invokerPackage) {
+ this.invokerPackage = invokerPackage;
+ }
+
+ public String getApiPackage() {
+ return apiPackage;
+ }
+
+ public void setApiPackage(String apiPackage) {
+ this.apiPackage = apiPackage;
+ }
+
+ public String getModelPackage() {
+ return modelPackage;
+ }
+
+ public void setModelPackage(String modelPackage) {
+ this.modelPackage = modelPackage;
+ }
+
+ public String getTargetXmlnsNamespace() {
+ return targetXmlnsNamespace;
+ }
+
+ public void setTargetXmlnsNamespace(String targetXmlnsNamespace) {
+ this.targetXmlnsNamespace = targetXmlnsNamespace;
+ }
+
+ Map toConfigOptionsProperties() {
+
+ Map configOptionsProperties = new HashMap<>();
+ configOptionsProperties.put(PREFIX, prefix);
+ configOptionsProperties.put(API_ENDPOINT, qualifiedEndpoint());
+ configOptionsProperties.put(API_TYPE, type.toString());
+ configOptionsProperties.put(TARGET_XMLNS_NAMESPACE,
+ replaceDynamicVarsToLowerCase(targetXmlnsNamespace, prefix, version));
+ configOptionsProperties.put("invokerPackage",
+ replaceDynamicVarsToLowerCase(invokerPackage, prefix, version));
+ configOptionsProperties.put("apiPackage",
+ replaceDynamicVarsToLowerCase(apiPackage, prefix, version));
+ configOptionsProperties.put("modelPackage",
+ replaceDynamicVarsToLowerCase(modelPackage, prefix, version));
+ configOptionsProperties.put("useTags", useTags);
+
+ return configOptionsProperties;
+ }
+
+ }
+
+
+}
diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoIntegrationTest.java b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoIntegrationTest.java
new file mode 100644
index 0000000000..d4bcc740b9
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoIntegrationTest.java
@@ -0,0 +1,314 @@
+package org.citrusframework.maven.plugin;
+
+import static com.google.common.collect.Streams.concat;
+import static java.lang.Boolean.TRUE;
+import static java.util.Arrays.stream;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.CITRUS_TEST_SCHEMA;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.replaceDynamicVars;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.replaceDynamicVarsToLowerCase;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.springframework.test.util.ReflectionTestUtils.getField;
+
+import jakarta.validation.constraints.NotNull;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.testing.AbstractMojoTestCase;
+import org.apache.maven.project.MavenProject;
+import org.assertj.core.api.Assertions;
+import org.citrusframework.exceptions.CitrusRuntimeException;
+import org.citrusframework.exceptions.TestCaseFailedException;
+import org.citrusframework.maven.plugin.TestApiGeneratorMojo.ApiConfig;
+import org.citrusframework.maven.plugin.TestApiGeneratorMojo.ApiType;
+import org.citrusframework.maven.plugin.stubs.CitrusOpenApiGeneratorMavenProjectStub;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * @author Thorsten Schlathoelter
+ */
+class TestApiGeneratorMojoIntegrationTest extends AbstractMojoTestCase {
+
+ public static final String OTHER_META_FILE_CONTENT = "somenamespace=somevalue";
+
+ public static final String OTHER_CITRUS_META_FILE_CONTENT = String.format("somenamespace/%s/aa=somevalue", CITRUS_TEST_SCHEMA);
+
+ /**
+ * Array containing path templates for each generated file, specified with tokens. Tokens can be replaced with values of the respective
+ * testing scenario.
+ */
+ private static final String[] STANDARD_FILE_PATH_TEMPLATES = new String[]{
+ "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%INVOKER_FOLDER%/citrus/extension/%CAMEL_PREFIX%NamespaceHandler.java",
+ "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%INVOKER_FOLDER%/citrus/%CAMEL_PREFIX%AbstractTestRequest.java",
+ "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%INVOKER_FOLDER%/citrus/%CAMEL_PREFIX%BeanDefinitionParser.java",
+ "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%INVOKER_FOLDER%/spring/%CAMEL_PREFIX%BeanConfiguration.java",
+ "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%MODEL_FOLDER%/PingReqType.java",
+ "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%MODEL_FOLDER%/PingRespType.java",
+ "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%REQUEST_FOLDER%/PingApi.java",
+ "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%REQUEST_FOLDER%/PungApi.java",
+ "%TARGET_FOLDER%/%GENERATED_RESOURCES_FOLDER%/%SCHEMA_FOLDER%/%LOWER_PREFIX%-api.xsd",
+ "%TARGET_FOLDER%/%GENERATED_RESOURCES_FOLDER%/%LOWER_PREFIX%-api-model.csv"
+ };
+
+ /**
+ * Array containing path templates for each generated spring meta file, specified with tokens. Tokens can be replaced with values of the respective
+ * testing scenario.
+ */
+ private static final String[] SPRING_META_FILE_TEMPLATES = new String[]{
+ "%BASE_FOLDER%/%META_INF_FOLDER%/spring.handlers",
+ "%BASE_FOLDER%/%META_INF_FOLDER%/spring.schemas"
+ };
+
+ private TestApiGeneratorMojo fixture;
+
+ @BeforeEach
+ @SuppressWarnings("JUnitMixedFramework")
+ void beforeEachSetup() throws Exception {
+ setUp();
+ }
+
+ static Stream executeMojoWithConfigurations() {
+ return Stream.of(
+ arguments("pom-missing-prefix",
+ new MojoExecutionException("Required parameter 'prefix' not set for api at index '0'!")),
+ arguments("pom-missing-source",
+ new MojoExecutionException("Required parameter 'source' not set for api at index '0'!")),
+ arguments("pom-minimal-config", null),
+ arguments("pom-minimal-with-version-config", null),
+ arguments("pom-multi-config", null),
+ arguments("pom-full-config", null),
+ arguments("pom-full-with-version-config", null),
+ arguments("pom-soap-config", null)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void executeMojoWithConfigurations(String configName, Exception expectedException)
+ throws Exception {
+
+ try {
+ fixture = fixtureFromPom(configName);
+ } catch (MojoExecutionException | MojoFailureException e) {
+ Assertions.fail("Test setup failed!", e);
+ }
+
+ @SuppressWarnings("unchecked")
+ List apiConfigs = (List) getField(fixture, "apis");
+
+ assertThat(apiConfigs).isNotNull();
+
+ if (expectedException == null) {
+ // Given
+ writeSomeValuesToSpringMetaFiles(apiConfigs);
+
+ // When
+ assertThatCode(() -> fixture.execute()).doesNotThrowAnyException();
+
+ // Then
+ for (ApiConfig apiConfig : apiConfigs) {
+ assertFilesGenerated(apiConfig);
+ assertSpecificFileContent(apiConfig);
+ }
+ } else {
+ // When/Then
+ assertThatThrownBy(() -> fixture.execute()).isInstanceOf(expectedException.getClass())
+ .hasMessage(expectedException.getMessage());
+ }
+ }
+
+ /**
+ * Writes values to spring meta files, to make sure existing non generated and existing generated values are treated properly.
+ */
+ private void writeSomeValuesToSpringMetaFiles(List apiConfigs) {
+ for (ApiConfig apiConfig : apiConfigs) {
+ for (String filePathTemplate : SPRING_META_FILE_TEMPLATES) {
+
+ String filePath = resolveFilePath(apiConfig, filePathTemplate);
+ File file = new File(filePath);
+ if (!file.getParentFile().exists() && !new File(filePath).getParentFile().mkdirs()) {
+ Assertions.fail("Unable to prepare test data.");
+ }
+
+ try (FileWriter fileWriter = new FileWriter(filePath)) {
+ fileWriter.append(String.format("%s%n", OTHER_META_FILE_CONTENT));
+ fileWriter.append(String.format("%s%n", OTHER_CITRUS_META_FILE_CONTENT));
+ } catch (IOException e) {
+ throw new CitrusRuntimeException("Unable to write spring meta files", e);
+ }
+ }
+ }
+ }
+
+ private void assertFilesGenerated(ApiConfig apiConfig) {
+
+ for (String filePathTemplate : STANDARD_FILE_PATH_TEMPLATES) {
+ String filePath = resolveFilePath(apiConfig, filePathTemplate);
+ assertThat(new File(filePath)).isFile().exists();
+ }
+
+ if (TRUE.equals(getField(fixture, "generateSpringIntegrationFiles"))) {
+ for (String filePathTemplate : SPRING_META_FILE_TEMPLATES) {
+ String filePath = resolveFilePath(apiConfig, filePathTemplate);
+ assertThat(new File(filePath)).isFile().exists();
+ }
+ }
+ }
+
+ private void assertSpecificFileContent(ApiConfig apiConfig) {
+ try {
+ assertEndpointName(apiConfig);
+ assertTargetNamespace(apiConfig);
+ assertApiType(apiConfig);
+ assertSchemasInSpringSchemas(apiConfig);
+ assertHandlersInSpringHandlers(apiConfig);
+ } catch (IOException e) {
+ throw new TestCaseFailedException(e);
+ }
+ }
+
+ private void assertHandlersInSpringHandlers(ApiConfig apiConfig) throws IOException {
+ String targetNamespace = replaceDynamicVarsToLowerCase(apiConfig.getTargetXmlnsNamespace(), apiConfig.getPrefix(), apiConfig.getVersion());
+ targetNamespace = targetNamespace.replace(":", "\\:");
+ String invokerPackage = replaceDynamicVarsToLowerCase(apiConfig.getInvokerPackage(), apiConfig.getPrefix(), apiConfig.getVersion());
+
+ String text = String.format("%s=%s.citrus.extension.%sNamespaceHandler", targetNamespace, invokerPackage, apiConfig.getPrefix());
+
+ assertThat(getContentOfFile(apiConfig, "spring.handlers")).contains(text);
+
+ // Other specific meta info should be retained
+ assertThat(getContentOfFile(apiConfig, "spring.handlers")).contains(OTHER_META_FILE_CONTENT);
+ // Other citrus generated meta info should be deleted
+ assertThat(getContentOfFile(apiConfig, "spring.handlers")).doesNotContain(OTHER_CITRUS_META_FILE_CONTENT);
+ }
+
+ private void assertSchemasInSpringSchemas(ApiConfig apiConfig) throws IOException {
+
+ String targetNamespace = replaceDynamicVarsToLowerCase(apiConfig.getTargetXmlnsNamespace(), apiConfig.getPrefix(), apiConfig.getVersion());
+ targetNamespace = targetNamespace.replace(":", "\\:");
+ String schemaPath = replaceDynamicVarsToLowerCase((String)getField(fixture, "schemaFolder"), apiConfig.getPrefix(), apiConfig.getVersion());
+
+ String text = String.format("%s.xsd=%s/%s-api.xsd", targetNamespace, schemaPath, apiConfig.getPrefix().toLowerCase());
+
+ // Other specific meta info should be retained assertThat(getContentOfFile(apiConfig, "spring.schemas")).contains(OTHER_META_FILE_CONTENT);
+ assertThat(getContentOfFile(apiConfig, "spring.schemas")).contains(String.format("%s", text));
+ // Other citrus generated meta info should be deleted
+ assertThat(getContentOfFile(apiConfig, "spring.schemas")).doesNotContain(OTHER_CITRUS_META_FILE_CONTENT);
+ }
+
+ private void assertApiType(ApiConfig apiConfig) throws IOException {
+ String text;
+ switch (apiConfig.getType()) {
+ case REST -> text = "HttpClient httpClient";
+ case SOAP -> text = "WebServiceClient wsClient";
+ default -> throw new IllegalArgumentException(String.format("No apiTye set in ApiConfig. Expected one of %s",
+ stream(ApiType.values()).map(ApiType::toString).collect(
+ Collectors.joining())));
+ }
+ assertThat(getContentOfFile(apiConfig, "AbstractTestRequest.java")).contains(text);
+ }
+
+ private void assertTargetNamespace(ApiConfig apiConfig) throws IOException {
+ assertThat(getContentOfFile(apiConfig, "-api.xsd")).contains(
+ String.format("targetNamespace=\"%s\"",
+ replaceDynamicVarsToLowerCase(apiConfig.getTargetXmlnsNamespace(), apiConfig.getPrefix(), apiConfig.getVersion())));
+ }
+
+ private void assertEndpointName(ApiConfig apiConfig) throws IOException {
+ assertThat(getContentOfFile(apiConfig, "AbstractTestRequest")).contains(
+ String.format("@Qualifier(\"%s\")", apiConfig.qualifiedEndpoint()));
+ }
+
+ private String getContentOfFile(ApiConfig apiConfig, String fileIdentifier) throws IOException {
+ String filePathTemplate = getTemplateContaining(fileIdentifier);
+ String filePath = resolveFilePath(apiConfig, filePathTemplate);
+
+ File file = new File(filePath);
+
+ assertThat(file).exists();
+ Path path = Paths.get(filePath);
+ byte[] bytes = Files.readAllBytes(path);
+ return new String(bytes, StandardCharsets.UTF_8);
+ }
+
+ private String getTemplateContaining(String text) {
+ return concat(stream(STANDARD_FILE_PATH_TEMPLATES), stream(SPRING_META_FILE_TEMPLATES))
+ .filter(path -> path.contains(text)).findFirst()
+ .orElseThrow(() -> new AssertionError(String.format("Can't find file template with content: '%s'", text)));
+ }
+
+ @NotNull
+ private String resolveFilePath(ApiConfig apiConfig, String filePathTemplate) {
+
+ String lowerCasePrefix = apiConfig.getPrefix().toLowerCase();
+ char[] prefixCharArray = apiConfig.getPrefix().toCharArray();
+ prefixCharArray[0] = Character.toUpperCase(prefixCharArray[0]);
+ String camelCasePrefix = new String(prefixCharArray);
+
+ String invokerFolder = toFolder(
+ replaceDynamicVarsToLowerCase(apiConfig.getInvokerPackage(), apiConfig.getPrefix(), apiConfig.getVersion()));
+ String modelFolder = toFolder(
+ replaceDynamicVarsToLowerCase(apiConfig.getModelPackage(), apiConfig.getPrefix(), apiConfig.getVersion()));
+ String requestFolder = toFolder(
+ replaceDynamicVarsToLowerCase(apiConfig.getApiPackage(), apiConfig.getPrefix(), apiConfig.getVersion()));
+ String schemaFolder = toFolder(
+ replaceDynamicVars((String)getField(fixture, "schemaFolder"), apiConfig.getPrefix(), apiConfig.getVersion()));
+ String generatedSourcesFolder = toFolder(
+ replaceDynamicVars((String)getField(fixture, "sourceFolder"), apiConfig.getPrefix(), apiConfig.getVersion()));
+ String generatedResourcesFolder = toFolder(
+ replaceDynamicVars((String)getField(fixture, "resourceFolder"), apiConfig.getPrefix(), apiConfig.getVersion()));
+
+ return filePathTemplate
+ .replace("%BASE_FOLDER%", fixture.getMavenProject().getBasedir().getPath())
+ .replace("%TARGET_FOLDER%", fixture.getMavenProject().getBuild().getDirectory())
+ .replace("%SOURCE_FOLDER%", fixture.getMavenProject().getBuild().getSourceDirectory())
+ .replace("%GENERATED_SOURCES_FOLDER%", generatedSourcesFolder)
+ .replace("%GENERATED_RESOURCES_FOLDER%", generatedResourcesFolder)
+ .replace("%INVOKER_FOLDER%", invokerFolder)
+ .replace("%MODEL_FOLDER%", modelFolder)
+ .replace("%REQUEST_FOLDER%", requestFolder)
+ .replace("%SCHEMA_FOLDER%", schemaFolder)
+ .replace("%LOWER_PREFIX%", lowerCasePrefix)
+ .replace("%CAMEL_PREFIX%", camelCasePrefix)
+ .replace("%META_INF_FOLDER%", toFolder((String) getField(fixture, "metaInfFolder")));
+ }
+
+ private String toFolder(String text) {
+
+ if (text == null) {
+ return "";
+ }
+
+ return text.replace(".", "/");
+ }
+
+ private TestApiGeneratorMojo fixtureFromPom(String configName) throws Exception {
+ String goal = "create-test-api";
+
+ File pomFile = new File(getBasedir(), String.format("src/test/resources/%s/%s", getClass().getSimpleName(), configName + ".xml"));
+ assertThat(pomFile).exists();
+
+ MavenProject mavenProject = new CitrusOpenApiGeneratorMavenProjectStub(configName);
+
+ TestApiGeneratorMojo testApiGeneratorMojo = (TestApiGeneratorMojo) lookupMojo(goal, pomFile);
+ testApiGeneratorMojo.setMavenProject(mavenProject);
+ testApiGeneratorMojo.setMojoExecution(newMojoExecution(goal));
+
+ return testApiGeneratorMojo;
+ }
+
+}
diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoUnitTest.java b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoUnitTest.java
new file mode 100644
index 0000000000..c63753525f
--- /dev/null
+++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoUnitTest.java
@@ -0,0 +1,286 @@
+package org.citrusframework.maven.plugin;
+
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_API_PACKAGE;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_API_TYPE;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_INVOKER_PACKAGE;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_META_INF_FOLDER;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_MODEL_PACKAGE;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_RESOURCE_FOLDER;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_SCHEMA_FOLDER_TEMPLATE;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_SOURCE_FOLDER;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_TARGET_NAMESPACE_TEMPLATE;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.replaceDynamicVars;
+import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.replaceDynamicVarsToLowerCase;
+import static java.lang.Boolean.TRUE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.mockito.Mockito.doReturn;
+import static org.springframework.test.util.ReflectionTestUtils.getField;
+
+import jakarta.validation.constraints.NotNull;
+import org.citrusframework.maven.plugin.TestApiGeneratorMojo;
+import org.citrusframework.maven.plugin.TestApiGeneratorMojo.ApiConfig;
+import org.citrusframework.maven.plugin.TestApiGeneratorMojo.ApiType;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.apache.maven.model.Build;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.testing.AbstractMojoTestCase;
+import org.apache.maven.project.MavenProject;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.openapitools.codegen.plugin.CodeGenMojo;
+
+
+/**
+ * @author Thorsten Schlathoelter
+ */
+@ExtendWith(MockitoExtension.class)
+@SuppressWarnings({"JUnitMalformedDeclaration", "JUnitMixedFramework"})
+class TestApiGeneratorMojoUnitTest extends AbstractMojoTestCase {
+
+ private TestApiGeneratorMojo fixture;
+
+ @Mock
+ private Build buildMock;
+
+ @Mock
+ private MavenProject mavenProjectMock;
+
+ @Mock
+ private MojoExecution mojoExecutionMock;
+
+ @BeforeEach
+ void beforeEach() {
+ fixture = new TestApiGeneratorMojo();
+ }
+
+ static Stream replaceDynamicVarsInPattern() {
+ return Stream.of(
+ arguments("%PREFIX%-aa-%VERSION%", "MyPrefix", "1", false, "MyPrefix-aa-1"),
+ arguments("%PREFIX%-aa-%VERSION%", "MyPrefix", null, false, "MyPrefix-aa"),
+ arguments("%PREFIX%/aa/%VERSION%", "MyPrefix", "1", false, "MyPrefix/aa/1"),
+ arguments("%PREFIX%/aa/%VERSION%", "MyPrefix", null, false, "MyPrefix/aa"),
+ arguments("%PREFIX%.aa.%VERSION%", "MyPrefix", "1", true, "myprefix.aa.1"),
+ arguments("%PREFIX%.aa.%VERSION%", "MyPrefix", null, true, "myprefix.aa")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void replaceDynamicVarsInPattern(String pattern, String prefix, String version, boolean toLowerCasePrefix, String expectedResult) {
+
+ if (toLowerCasePrefix) {
+ assertThat(
+ replaceDynamicVarsToLowerCase(pattern, prefix, version)).isEqualTo(expectedResult);
+ } else {
+ assertThat(
+ replaceDynamicVars(pattern, prefix, version)).isEqualTo(expectedResult);
+ }
+ }
+
+ static Stream configureMojo() {
+ return Stream.of(
+ arguments("DefaultConfigWithoutVersion", createMinimalApiConfig(null),
+ createMinimalCodeGenMojoParams(
+ "schema/xsd",
+ "org.citrusframework.automation.mydefaultprefix",
+ "org.citrusframework.automation.mydefaultprefix.model",
+ "org.citrusframework.automation.mydefaultprefix.api",
+ "http://www.citrusframework.org/citrus-test-schema/mydefaultprefix-api"
+ )),
+ arguments("DefaultConfigWithVersion", createMinimalApiConfig("v1"),
+ createMinimalCodeGenMojoParams(
+ "schema/xsd/v1",
+ "org.citrusframework.automation.mydefaultprefix.v1",
+ "org.citrusframework.automation.mydefaultprefix.v1.model",
+ "org.citrusframework.automation.mydefaultprefix.v1.api",
+ "http://www.citrusframework.org/citrus-test-schema/v1/mydefaultprefix-api"
+ )),
+ arguments("CustomConfigWithoutVersion", createFullApiConfig(null),
+ createCustomCodeGenMojoParams(
+ "schema/xsd",
+ "my.mycustomprefix.invoker.package",
+ "my.mycustomprefix.model.package",
+ "my.mycustomprefix.api.package",
+ "myNamespace/citrus-test-schema/mycustomprefix"
+ )),
+ arguments("CustomConfigWithVersion", createFullApiConfig("v1"),
+ createCustomCodeGenMojoParams(
+ "schema/xsd/v1",
+ "my.mycustomprefix.v1.invoker.package",
+ "my.mycustomprefix.v1.model.package",
+ "my.mycustomprefix.v1.api.package",
+ "myNamespace/citrus-test-schema/mycustomprefix/v1"
+ ))
+ );
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource
+ void configureMojo(String name, ApiConfig apiConfig, CodeGenMojoParams controlParams) throws MojoExecutionException {
+ doReturn("target").when(buildMock).getDirectory();
+ doReturn(buildMock).when(mavenProjectMock).getBuild();
+ fixture.setMavenProject(mavenProjectMock);
+ fixture.setMojoExecution(mojoExecutionMock);
+
+ CodeGenMojo codeGenMojo = fixture.configureCodeGenMojo(apiConfig);
+ assertThat(getField(codeGenMojo, "project")).isEqualTo(mavenProjectMock);
+ assertThat(getField(codeGenMojo, "mojo")).isEqualTo(mojoExecutionMock);
+ assertThat(((File) getField(codeGenMojo, "output"))).hasName(controlParams.output);
+ assertThat(getField(codeGenMojo, "inputSpec")).isEqualTo(controlParams.source);
+ assertThat(getField(codeGenMojo, "generateSupportingFiles")).isEqualTo(TRUE);
+ assertThat(getField(codeGenMojo, "generatorName")).isEqualTo("java-citrus");
+
+ //noinspection unchecked
+ assertThat((Map