From 12944f8883c66789afdb4bf942be652ff1a54e4e Mon Sep 17 00:00:00 2001
From: Pablo Bastidas <pablo.bastidas@leaseplan.com>
Date: Fri, 28 May 2021 15:59:56 +0200
Subject: [PATCH] Adding Api per operation option to JaxRS and Spring
 generators.

---
 docs/generators/java-msf4j.md                 |  1 +
 docs/generators/jaxrs-cxf-cdi.md              |  1 +
 docs/generators/jaxrs-cxf-extended.md         |  1 +
 docs/generators/jaxrs-cxf.md                  |  1 +
 docs/generators/jaxrs-jersey.md               |  1 +
 docs/generators/jaxrs-resteasy-eap.md         |  1 +
 docs/generators/jaxrs-resteasy.md             |  1 +
 docs/generators/jaxrs-spec.md                 |  1 +
 docs/generators/spring.md                     |  1 +
 .../AbstractJavaJAXRSServerCodegen.java       | 15 +++++
 .../languages/JavaCXFServerCodegen.java       |  1 +
 .../codegen/languages/SpringCodegen.java      | 57 +++++++++++++------
 .../codegen/utils/ProcessUtils.java           | 21 +++++++
 .../jaxrs/JavaJAXRSSpecServerCodegenTest.java | 30 ++++++++++
 .../java/spring/SpringCodegenTest.java        | 31 ++++++++++
 .../codegen/utils/ProcessUtilsTest.java       | 33 +++++++++++
 16 files changed, 179 insertions(+), 18 deletions(-)
 create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/ProcessUtilsTest.java

diff --git a/docs/generators/java-msf4j.md b/docs/generators/java-msf4j.md
index e0d62c36cd7..06794c88e41 100644
--- a/docs/generators/java-msf4j.md
+++ b/docs/generators/java-msf4j.md
@@ -11,6 +11,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
 |additionalModelTypeAnnotations|Additional annotations for model type(class level annotations)| |null|
 |allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
 |apiPackage|package for generated api classes| |org.openapitools.api|
+|apiPerOperation|Create interface and controller per operation| |false|
 |artifactDescription|artifact description in generated pom.xml| |OpenAPI Java|
 |artifactId|artifactId in generated pom.xml. This also becomes part of the generated library's filename| |openapi-jaxrs-server|
 |artifactUrl|artifact URL in generated pom.xml| |https://github.com/openapitools/openapi-generator|
diff --git a/docs/generators/jaxrs-cxf-cdi.md b/docs/generators/jaxrs-cxf-cdi.md
index 7cd7ed52c80..70a5d77c807 100644
--- a/docs/generators/jaxrs-cxf-cdi.md
+++ b/docs/generators/jaxrs-cxf-cdi.md
@@ -11,6 +11,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
 |additionalModelTypeAnnotations|Additional annotations for model type(class level annotations)| |null|
 |allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
 |apiPackage|package for generated api classes| |org.openapitools.api|
+|apiPerOperation|Create interface and controller per operation| |false|
 |artifactDescription|artifact description in generated pom.xml| |OpenAPI Java|
 |artifactId|artifactId in generated pom.xml. This also becomes part of the generated library's filename| |openapi-jaxrs-cxf-cdi-server|
 |artifactUrl|artifact URL in generated pom.xml| |https://github.com/openapitools/openapi-generator|
diff --git a/docs/generators/jaxrs-cxf-extended.md b/docs/generators/jaxrs-cxf-extended.md
index 39c9dcba665..f09a3ae45b9 100644
--- a/docs/generators/jaxrs-cxf-extended.md
+++ b/docs/generators/jaxrs-cxf-extended.md
@@ -12,6 +12,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
 |additionalModelTypeAnnotations|Additional annotations for model type(class level annotations)| |null|
 |allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
 |apiPackage|package for generated api classes| |org.openapitools.api|
+|apiPerOperation|Create interface and controller per operation| |false|
 |artifactDescription|artifact description in generated pom.xml| |OpenAPI Java|
 |artifactId|artifactId in generated pom.xml. This also becomes part of the generated library's filename| |openapi-cxf-server|
 |artifactUrl|artifact URL in generated pom.xml| |https://github.com/openapitools/openapi-generator|
diff --git a/docs/generators/jaxrs-cxf.md b/docs/generators/jaxrs-cxf.md
index 915ec4a96b1..f882fb1785b 100644
--- a/docs/generators/jaxrs-cxf.md
+++ b/docs/generators/jaxrs-cxf.md
@@ -12,6 +12,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
 |additionalModelTypeAnnotations|Additional annotations for model type(class level annotations)| |null|
 |allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
 |apiPackage|package for generated api classes| |org.openapitools.api|
+|apiPerOperation|Create interface and controller per operation| |false|
 |artifactDescription|artifact description in generated pom.xml| |OpenAPI Java|
 |artifactId|artifactId in generated pom.xml. This also becomes part of the generated library's filename| |openapi-cxf-server|
 |artifactUrl|artifact URL in generated pom.xml| |https://github.com/openapitools/openapi-generator|
diff --git a/docs/generators/jaxrs-jersey.md b/docs/generators/jaxrs-jersey.md
index e79a378b7be..876d46dd413 100644
--- a/docs/generators/jaxrs-jersey.md
+++ b/docs/generators/jaxrs-jersey.md
@@ -11,6 +11,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
 |additionalModelTypeAnnotations|Additional annotations for model type(class level annotations)| |null|
 |allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
 |apiPackage|package for generated api classes| |org.openapitools.api|
+|apiPerOperation|Create interface and controller per operation| |false|
 |artifactDescription|artifact description in generated pom.xml| |OpenAPI Java|
 |artifactId|artifactId in generated pom.xml. This also becomes part of the generated library's filename| |openapi-jaxrs-server|
 |artifactUrl|artifact URL in generated pom.xml| |https://github.com/openapitools/openapi-generator|
diff --git a/docs/generators/jaxrs-resteasy-eap.md b/docs/generators/jaxrs-resteasy-eap.md
index c7b5fdd5c1e..231413ee7dc 100644
--- a/docs/generators/jaxrs-resteasy-eap.md
+++ b/docs/generators/jaxrs-resteasy-eap.md
@@ -11,6 +11,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
 |additionalModelTypeAnnotations|Additional annotations for model type(class level annotations)| |null|
 |allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
 |apiPackage|package for generated api classes| |org.openapitools.api|
+|apiPerOperation|Create interface and controller per operation| |false|
 |artifactDescription|artifact description in generated pom.xml| |OpenAPI Java|
 |artifactId|artifactId in generated pom.xml. This also becomes part of the generated library's filename| |openapi-jaxrs-resteasy-eap-server|
 |artifactUrl|artifact URL in generated pom.xml| |https://github.com/openapitools/openapi-generator|
diff --git a/docs/generators/jaxrs-resteasy.md b/docs/generators/jaxrs-resteasy.md
index 4be73c6a6d1..73eb3256725 100644
--- a/docs/generators/jaxrs-resteasy.md
+++ b/docs/generators/jaxrs-resteasy.md
@@ -11,6 +11,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
 |additionalModelTypeAnnotations|Additional annotations for model type(class level annotations)| |null|
 |allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
 |apiPackage|package for generated api classes| |org.openapitools.api|
+|apiPerOperation|Create interface and controller per operation| |false|
 |artifactDescription|artifact description in generated pom.xml| |OpenAPI Java|
 |artifactId|artifactId in generated pom.xml. This also becomes part of the generated library's filename| |openapi-jaxrs-resteasy-server|
 |artifactUrl|artifact URL in generated pom.xml| |https://github.com/openapitools/openapi-generator|
diff --git a/docs/generators/jaxrs-spec.md b/docs/generators/jaxrs-spec.md
index 1b0425bdfc0..abcca224643 100644
--- a/docs/generators/jaxrs-spec.md
+++ b/docs/generators/jaxrs-spec.md
@@ -11,6 +11,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
 |additionalModelTypeAnnotations|Additional annotations for model type(class level annotations)| |null|
 |allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
 |apiPackage|package for generated api classes| |org.openapitools.api|
+|apiPerOperation|Create interface and controller per operation| |false|
 |artifactDescription|artifact description in generated pom.xml| |OpenAPI Java|
 |artifactId|artifactId in generated pom.xml. This also becomes part of the generated library's filename| |openapi-jaxrs-server|
 |artifactUrl|artifact URL in generated pom.xml| |https://github.com/openapitools/openapi-generator|
diff --git a/docs/generators/spring.md b/docs/generators/spring.md
index 43614a0a5aa..b393f3ea524 100644
--- a/docs/generators/spring.md
+++ b/docs/generators/spring.md
@@ -12,6 +12,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
 |allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
 |apiFirst|Generate the API from the OAI spec at server compile time (API first approach)| |false|
 |apiPackage|package for generated api classes| |org.openapitools.api|
+|apiPerOperation|create one interface and controller classnames per operation| |false|
 |artifactDescription|artifact description in generated pom.xml| |OpenAPI Java|
 |artifactId|artifactId in generated pom.xml. This also becomes part of the generated library's filename| |openapi-spring|
 |artifactUrl|artifact URL in generated pom.xml| |https://github.com/openapitools/openapi-generator|
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaJAXRSServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaJAXRSServerCodegen.java
index 8ee05fcd3e4..719f92eceaf 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaJAXRSServerCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaJAXRSServerCodegen.java
@@ -24,6 +24,7 @@ import io.swagger.v3.oas.models.PathItem;
 import org.apache.commons.lang3.StringUtils;
 import org.openapitools.codegen.*;
 import org.openapitools.codegen.languages.features.BeanValidationFeatures;
+import org.openapitools.codegen.utils.ProcessUtils;
 import org.openapitools.codegen.utils.URLPathUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,6 +36,7 @@ import java.util.*;
 public abstract class AbstractJavaJAXRSServerCodegen extends AbstractJavaCodegen implements BeanValidationFeatures {
     public static final String SERVER_PORT = "serverPort";
     public static final String USE_TAGS = "useTags";
+    public static final String API_PER_OPERATION = "apiPerOperation";
 
     /**
      * Name of the sub-directory in "src/main/resource" where to find the
@@ -48,6 +50,7 @@ public abstract class AbstractJavaJAXRSServerCodegen extends AbstractJavaCodegen
 
     protected boolean useBeanValidation = true;
     protected boolean useTags = false;
+    protected boolean apiPerOperation = false;
 
     private final Logger LOGGER = LoggerFactory.getLogger(AbstractJavaJAXRSServerCodegen.class);
 
@@ -77,6 +80,7 @@ public abstract class AbstractJavaJAXRSServerCodegen extends AbstractJavaCodegen
         cliOptions.add(CliOption.newBoolean(USE_BEANVALIDATION, "Use BeanValidation API annotations",useBeanValidation));
         cliOptions.add(new CliOption(SERVER_PORT, "The port on which the server should be started").defaultValue(serverPort));
         cliOptions.add(CliOption.newBoolean(USE_TAGS, "use tags for creating interface and controller classnames"));
+        cliOptions.add(CliOption.newBoolean(API_PER_OPERATION, "Create interface and controller per operation"));
     }
 
 
@@ -105,6 +109,10 @@ public abstract class AbstractJavaJAXRSServerCodegen extends AbstractJavaCodegen
             setUseTags(convertPropertyToBoolean(USE_TAGS));
         }
 
+        if (additionalProperties.containsKey(API_PER_OPERATION)) {
+            setApiPerOperation(convertPropertyToBoolean(API_PER_OPERATION));
+        }
+
         writePropertyBack(USE_BEANVALIDATION, useBeanValidation);
     }
 
@@ -116,6 +124,8 @@ public abstract class AbstractJavaJAXRSServerCodegen extends AbstractJavaCodegen
         }
         if (useTags) {
             super.addOperationToGroup(tag, resourcePath, operation, co, operations);
+        } else if (apiPerOperation) {
+            ProcessUtils.operationPerApi(resourcePath, co, operations);
         } else {
             co.baseName = basePath;
             if (StringUtils.isEmpty(co.baseName) || StringUtils.containsAny(co.baseName, "{", "}")) {
@@ -313,4 +323,9 @@ public abstract class AbstractJavaJAXRSServerCodegen extends AbstractJavaCodegen
     public void setUseTags(boolean useTags) {
         this.useTags = useTags;
     }
+
+    @VisibleForTesting
+    public void setApiPerOperation(boolean apiPerOperation) {
+        this.apiPerOperation = apiPerOperation;
+    }
 }
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaCXFServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaCXFServerCodegen.java
index 30c3479b626..c680d1dcda2 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaCXFServerCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaCXFServerCodegen.java
@@ -78,6 +78,7 @@ public class JavaCXFServerCodegen extends AbstractJavaJAXRSServerCodegen
         // clioOptions default redifinition need to be updated
         updateOption(CodegenConstants.ARTIFACT_ID, this.getArtifactId());
         updateOption(USE_TAGS, String.valueOf(true));
+        updateOption(API_PER_OPERATION, String.valueOf(false));
 
         apiTemplateFiles.put("apiServiceImpl.mustache", ".java");
 
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java
index 058acbed956..363140ea113 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java
@@ -40,6 +40,7 @@ import java.util.regex.Matcher;
 import java.util.stream.Collectors;
 
 import static org.apache.commons.lang3.StringUtils.isNotEmpty;
+import static org.openapitools.codegen.utils.ProcessUtils.operationPerApi;
 import static org.openapitools.codegen.utils.StringUtils.camelize;
 
 public class SpringCodegen extends AbstractJavaCodegen
@@ -62,6 +63,7 @@ public class SpringCodegen extends AbstractJavaCodegen
     public static final String REACTIVE = "reactive";
     public static final String RESPONSE_WRAPPER = "responseWrapper";
     public static final String USE_TAGS = "useTags";
+    public static final String API_PER_OPERATION = "apiPerOperation";
     public static final String SPRING_MVC_LIBRARY = "spring-mvc";
     public static final String SPRING_BOOT = "spring-boot";
     public static final String SPRING_CLOUD_LIBRARY = "spring-cloud";
@@ -88,6 +90,7 @@ public class SpringCodegen extends AbstractJavaCodegen
     protected String responseWrapper = "";
     protected boolean skipDefaultInterface = false;
     protected boolean useTags = false;
+    protected boolean apiPerOperation = false;
     protected boolean useBeanValidation = true;
     protected boolean performBeanValidation = false;
     protected boolean implicitHeaders = false;
@@ -162,6 +165,7 @@ public class SpringCodegen extends AbstractJavaCodegen
         cliOptions.add(new CliOption(RESPONSE_WRAPPER, "wrap the responses in given type (Future, Callable, CompletableFuture,ListenableFuture, DeferredResult, HystrixCommand, RxObservable, RxSingle or fully qualified type)"));
         cliOptions.add(CliOption.newBoolean(VIRTUAL_SERVICE, "Generates the virtual service. For more details refer - https://github.com/virtualansoftware/virtualan/wiki"));
         cliOptions.add(CliOption.newBoolean(USE_TAGS, "use tags for creating interface and controller classnames", useTags));
+        cliOptions.add(CliOption.newBoolean(API_PER_OPERATION, "create one interface and controller classnames per operation", useTags));
         cliOptions.add(CliOption.newBoolean(USE_BEANVALIDATION, "Use BeanValidation API annotations", useBeanValidation));
         cliOptions.add(CliOption.newBoolean(PERFORM_BEANVALIDATION, "Use Bean Validation Impl. to perform BeanValidation", performBeanValidation));
         cliOptions.add(CliOption.newBoolean(IMPLICIT_HEADERS, "Skip header parameters in the generated API methods using @ApiImplicitParams annotation.", implicitHeaders));
@@ -297,6 +301,10 @@ public class SpringCodegen extends AbstractJavaCodegen
             this.setUseTags(Boolean.parseBoolean(additionalProperties.get(USE_TAGS).toString()));
         }
 
+        if (additionalProperties.containsKey(API_PER_OPERATION)) {
+            this.setApiPerOperation(Boolean.parseBoolean(additionalProperties.get(API_PER_OPERATION).toString()));
+        }
+
         if (additionalProperties.containsKey(USE_BEANVALIDATION)) {
             this.setUseBeanValidation(convertPropertyToBoolean(USE_BEANVALIDATION));
         }
@@ -499,30 +507,39 @@ public class SpringCodegen extends AbstractJavaCodegen
     }
 
     @Override
-    public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co, Map<String, List<CodegenOperation>> operations) {
+    public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co,
+            Map<String, List<CodegenOperation>> operations) {
         if ((SPRING_BOOT.equals(library) || SPRING_MVC_LIBRARY.equals(library)) && !useTags) {
-            String basePath = resourcePath;
-            if (basePath.startsWith("/")) {
-                basePath = basePath.substring(1);
-            }
-            int pos = basePath.indexOf("/");
-            if (pos > 0) {
-                basePath = basePath.substring(0, pos);
-            }
-
-            if ("".equals(basePath)) {
-                basePath = "default";
-            } else {
-                co.subresourceOperation = !co.path.isEmpty();
-            }
-            List<CodegenOperation> opList = operations.computeIfAbsent(basePath, k -> new ArrayList<>());
-            opList.add(co);
-            co.baseName = basePath;
+         if (apiPerOperation) {
+             operationPerApi(resourcePath, co, operations);
+         } else {
+             operationPerBasePath(resourcePath, co, operations);
+         }
         } else {
             super.addOperationToGroup(tag, resourcePath, operation, co, operations);
         }
     }
 
+    private void operationPerBasePath(String resourcePath, CodegenOperation co, Map<String, List<CodegenOperation>> operations) {
+        String basePath = resourcePath;
+        if (basePath.startsWith("/")) {
+            basePath = basePath.substring(1);
+        }
+        int pos = basePath.indexOf("/");
+        if (pos > 0) {
+            basePath = basePath.substring(0, pos);
+        }
+
+        if ("".equals(basePath)) {
+            basePath = "default";
+        } else {
+            co.subresourceOperation = !co.path.isEmpty();
+        }
+        List<CodegenOperation> opList = operations.computeIfAbsent(basePath, k -> new ArrayList<>());
+        opList.add(co);
+        co.baseName = basePath;
+    }
+
     @Override
     public void preprocessOpenAPI(OpenAPI openAPI) {
         super.preprocessOpenAPI(openAPI);
@@ -788,6 +805,10 @@ public class SpringCodegen extends AbstractJavaCodegen
         this.useTags = useTags;
     }
 
+    public void setApiPerOperation(boolean apiPerOperation) {
+        this.apiPerOperation = apiPerOperation;
+    }
+
     public void setImplicitHeaders(boolean implicitHeaders) {
         this.implicitHeaders = implicitHeaders;
     }
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ProcessUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ProcessUtils.java
index 167c7d21124..b997ecd2b3e 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ProcessUtils.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ProcessUtils.java
@@ -1,8 +1,14 @@
 package org.openapitools.codegen.utils;
 
+import static org.openapitools.codegen.utils.StringUtils.camelize;
+
 import io.swagger.v3.oas.models.OpenAPI;
 import io.swagger.v3.oas.models.security.SecurityScheme;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.stream.Collectors;
 import org.openapitools.codegen.CodegenModel;
+import org.openapitools.codegen.CodegenOperation;
 import org.openapitools.codegen.CodegenProperty;
 import org.openapitools.codegen.CodegenSecurity;
 
@@ -252,5 +258,20 @@ public class ProcessUtils {
             return openAPI.getComponents() != null ? openAPI.getComponents().getSecuritySchemes() : null;
         }
     }
+
+    public static void operationPerApi(String resourcePath, CodegenOperation co, Map<String, List<CodegenOperation>> operations) {
+        String base = buildApiName(resourcePath, co.httpMethod);
+        List<CodegenOperation> opList = operations.computeIfAbsent(base, k -> new ArrayList<>());
+        opList.add(co);
+        co.baseName = base;
+    }
+
+    static String buildApiName(String resourcePath, String httpMethod) {
+        String base = Arrays.stream(resourcePath.split("/"))
+                .map(s -> s.replaceAll("([{}/])", ""))
+                .map(org.openapitools.codegen.utils.StringUtils::camelize)
+                .collect(Collectors.joining());
+        return base + camelize(httpMethod.toLowerCase(Locale.ROOT));
+    }
 }
 
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java
index 9d32f0929f7..ccac12530a2 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java
@@ -419,4 +419,34 @@ public class JavaJAXRSSpecServerCodegenTest extends JavaJaxrsBaseTest {
 
         assertFileContains(path, "\nimport java.util.Set;\n");
     }
+
+    @Test
+    public void givenTwoOperationAndApiPerOperationTrueWhenAddOperationThenAdd2OperationsWithExpectedName() {
+        String resourcePath = "/primaryresource";
+
+        CodegenOperation getOperation = buildOperation(resourcePath, "GET");
+        CodegenOperation putOperation = buildOperation(resourcePath, "PUT");
+
+        codegen.setApiPerOperation(true);
+
+        Operation operation = new Operation();
+        Map<String, List<CodegenOperation>> operationList = new HashMap<>();
+
+        codegen.addOperationToGroup("Primaryresource", resourcePath, operation, getOperation, operationList);
+        codegen.addOperationToGroup("Primaryresource", resourcePath, operation, putOperation, operationList);
+
+        Assert.assertEquals(operationList.size(), 2);
+        Assert.assertTrue(operationList.containsKey("PrimaryresourceGet"));
+        Assert.assertTrue(operationList.containsKey("PrimaryresourcePut"));
+        Assert.assertEquals(getOperation.baseName, "PrimaryresourceGet");
+        Assert.assertEquals(putOperation.baseName, "PrimaryresourcePut");
+    }
+
+    private CodegenOperation buildOperation(String resourcePath, String put) {
+        CodegenOperation putOperation = new CodegenOperation();
+        putOperation.httpMethod = put;
+        putOperation.path = resourcePath;
+        return putOperation;
+    }
+
 }
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java
index 3e8d8977209..c9c0e1d7695 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java
@@ -24,6 +24,7 @@ import io.swagger.v3.oas.models.info.Info;
 import io.swagger.v3.oas.models.media.Schema;
 import io.swagger.v3.oas.models.servers.Server;
 import io.swagger.v3.parser.core.models.ParseOptions;
+import java.util.HashMap;
 import org.openapitools.codegen.*;
 import org.openapitools.codegen.languages.SpringCodegen;
 import org.openapitools.codegen.languages.features.CXFServerFeatures;
@@ -653,4 +654,34 @@ public class SpringCodegenTest {
         assertFileNotContains(Paths.get(outputPath + "/src/main/java/org/openapitools/api/SomeApi.java"), "Mono<DummyRequest>");
         assertFileNotContains(Paths.get(outputPath + "/src/main/java/org/openapitools/api/SomeApiDelegate.java"), "Mono<DummyRequest>");
     }
+
+    @Test
+    public void givenTwoOperationAndApiPerOperationTrueWhenAddOperationThenAdd2OperationsWithExpectedName() {
+        SpringCodegen codegen = new SpringCodegen();
+        String resourcePath = "/primaryresource";
+
+        CodegenOperation getOperation = buildOperation(resourcePath, "GET");
+        CodegenOperation putOperation = buildOperation(resourcePath, "PUT");
+
+        codegen.setApiPerOperation(true);
+
+        Operation operation = new Operation();
+        Map<String, List<CodegenOperation>> operationList = new HashMap<>();
+
+        codegen.addOperationToGroup("Primaryresource", resourcePath, operation, getOperation, operationList);
+        codegen.addOperationToGroup("Primaryresource", resourcePath, operation, putOperation, operationList);
+
+        Assert.assertEquals(operationList.size(), 2);
+        Assert.assertTrue(operationList.containsKey("PrimaryresourceGet"));
+        Assert.assertTrue(operationList.containsKey("PrimaryresourcePut"));
+        Assert.assertEquals(getOperation.baseName, "PrimaryresourceGet");
+        Assert.assertEquals(putOperation.baseName, "PrimaryresourcePut");
+    }
+
+    private CodegenOperation buildOperation(String resourcePath, String put) {
+        CodegenOperation putOperation = new CodegenOperation();
+        putOperation.httpMethod = put;
+        putOperation.path = resourcePath;
+        return putOperation;
+    }
 }
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/ProcessUtilsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/ProcessUtilsTest.java
new file mode 100644
index 00000000000..43f8c0245ed
--- /dev/null
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/ProcessUtilsTest.java
@@ -0,0 +1,33 @@
+package org.openapitools.codegen.utils;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class ProcessUtilsTest {
+
+
+    @DataProvider(name = "pathMethodValues")
+    public static Object[][] dataProvider() {
+        return new Object[][]{
+                {"/pet", "POST", "PetPost"},
+                {"/pet", "PUT", "PetPut"},
+                {"/pet/findByStatus", "GET", "PetFindByStatusGet"},
+                {"/pet/findByTags", "GET", "PetFindByTagsGet"},
+                {"/pet/{petId}", "GET", "PetPetIdGet"},
+                {"/pet/{petId}", "POST", "PetPetIdPost"},
+                {"/pet/{petId}", "DELETE", "PetPetIdDelete"},
+                {"/pet/{petId}/uploadImage", "POST", "PetPetIdUploadImagePost"},
+                {"/store/order/{orderId}", "GET", "StoreOrderOrderIdGet"},
+                {"/store/order/{orderId}", "DELETE", "StoreOrderOrderIdDelete"},
+        };
+    }
+
+    @Test(dataProvider = "pathMethodValues")
+    public void givenPathAndMethodBuildApiName(String givenPath, String givenMethod, String expected) {
+        String result = ProcessUtils.buildApiName(givenPath, givenMethod);
+
+        assertEquals(result, expected);
+    }
+}
\ No newline at end of file
-- 
GitLab