Skip to content

Commit 336d10d

Browse files
rpanackala-dnewtork
authored
refactor: [OpenAPI] Extract Custom JavaClientCodegen to Separate Class (#739)
Co-authored-by: Alexander Dümont <[email protected]> Co-authored-by: Alexander Dümont <[email protected]> Co-authored-by: Roshin Rajan Panackal <[email protected]>
1 parent 8461bd2 commit 336d10d

File tree

2 files changed

+161
-139
lines changed

2 files changed

+161
-139
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package com.sap.cloud.sdk.datamodel.openapi.generator;
2+
3+
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.FIX_REDUNDANT_IS_BOOLEAN_PREFIX;
4+
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_FLOAT_ARRAYS;
5+
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_ONE_OF_CREATORS;
6+
7+
import java.util.HashSet;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Set;
11+
import java.util.function.Predicate;
12+
import java.util.regex.Pattern;
13+
14+
import javax.annotation.Nonnull;
15+
import javax.annotation.Nullable;
16+
17+
import org.openapitools.codegen.CodegenModel;
18+
import org.openapitools.codegen.CodegenOperation;
19+
import org.openapitools.codegen.CodegenProperty;
20+
import org.openapitools.codegen.languages.JavaClientCodegen;
21+
import org.openapitools.codegen.model.ModelMap;
22+
import org.openapitools.codegen.model.OperationsMap;
23+
24+
import com.sap.cloud.sdk.datamodel.openapi.generator.model.GenerationConfiguration;
25+
26+
import io.swagger.v3.oas.models.media.Schema;
27+
import lombok.extern.slf4j.Slf4j;
28+
29+
@Slf4j
30+
class CustomJavaClientCodegen extends JavaClientCodegen
31+
{
32+
private final GenerationConfiguration config;
33+
private static final Predicate<String> DOUBLE_IS_PATTERN = Pattern.compile("^isIs[A-Z]").asPredicate();
34+
private static final Set<String> PRIMITIVES = Set.of("String", "Integer", "Long", "Double", "Float", "Byte");
35+
36+
public CustomJavaClientCodegen( @Nonnull final GenerationConfiguration config )
37+
{
38+
this.config = config;
39+
}
40+
41+
@Override
42+
protected
43+
void
44+
updatePropertyForArray( @Nonnull final CodegenProperty property, @Nonnull final CodegenProperty innerProperty )
45+
{
46+
super.updatePropertyForArray(property, innerProperty);
47+
48+
if( USE_FLOAT_ARRAYS.isEnabled(config) && innerProperty.isNumber && property.isArray ) {
49+
property.datatypeWithEnum = "float[]";
50+
property.vendorExtensions.put("isPrimitiveArray", true);
51+
}
52+
}
53+
54+
@SuppressWarnings( { "rawtypes", "RedundantSuppression" } )
55+
@Override
56+
@Nullable
57+
public String toDefaultValue( @Nonnull final CodegenProperty cp, @Nonnull final Schema schema )
58+
{
59+
if( USE_FLOAT_ARRAYS.isEnabled(config) && "float[]".equals(cp.datatypeWithEnum) ) {
60+
return null;
61+
}
62+
return super.toDefaultValue(cp, schema);
63+
}
64+
65+
@Override
66+
@Nullable
67+
public String toBooleanGetter( @Nullable final String name )
68+
{
69+
final String result = super.toBooleanGetter(name);
70+
if( FIX_REDUNDANT_IS_BOOLEAN_PREFIX.isEnabled(config) && result != null && DOUBLE_IS_PATTERN.test(result) ) {
71+
return "is" + result.substring(4);
72+
}
73+
return result;
74+
}
75+
76+
// Custom processor to inject "x-return-nullable" extension
77+
@Override
78+
@Nonnull
79+
public
80+
OperationsMap
81+
postProcessOperationsWithModels( @Nonnull final OperationsMap ops, @Nonnull final List<ModelMap> allModels )
82+
{
83+
for( final CodegenOperation op : ops.getOperations().getOperation() ) {
84+
final var noContent =
85+
op.isResponseOptional
86+
|| op.responses == null
87+
|| op.responses.stream().anyMatch(r -> "204".equals(r.code));
88+
op.vendorExtensions.put("x-return-nullable", op.returnType != null && noContent);
89+
}
90+
return super.postProcessOperationsWithModels(ops, allModels);
91+
}
92+
93+
@SuppressWarnings( { "rawtypes", "RedundantSuppression" } )
94+
@Override
95+
protected void updateModelForComposedSchema(
96+
@Nonnull final CodegenModel m,
97+
@Nonnull final Schema schema,
98+
@Nonnull final Map<String, Schema> allDefinitions )
99+
{
100+
super.updateModelForComposedSchema(m, schema, allDefinitions);
101+
102+
if( USE_ONE_OF_CREATORS.isEnabled(config) ) {
103+
useCreatorsForInterfaceSubtypes(m);
104+
}
105+
}
106+
107+
/**
108+
* Use JsonCreator for interface sub-types in case there are any primitives.
109+
*
110+
* @param m
111+
* The model to update.
112+
*/
113+
private void useCreatorsForInterfaceSubtypes( @Nonnull final CodegenModel m )
114+
{
115+
if( m.discriminator != null ) {
116+
return;
117+
}
118+
boolean useCreators = false;
119+
for( final Set<String> candidates : List.of(m.anyOf, m.oneOf) ) {
120+
int nonPrimitives = 0;
121+
final var candidatesSingle = new HashSet<String>();
122+
final var candidatesMultiple = new HashSet<String>();
123+
124+
for( final String candidate : candidates ) {
125+
if( candidate.startsWith("List<") ) {
126+
final var c1 = candidate.substring(5, candidate.length() - 1);
127+
candidatesMultiple.add(c1);
128+
useCreators = true;
129+
} else {
130+
candidatesSingle.add(candidate);
131+
useCreators |= PRIMITIVES.contains(candidate);
132+
if( !PRIMITIVES.contains(candidate) ) {
133+
nonPrimitives++;
134+
}
135+
}
136+
}
137+
if( useCreators ) {
138+
if( nonPrimitives > 1 ) {
139+
final var msg =
140+
"Generating interface with mixed multiple non-primitive and primitive sub-types: {}. Deserialization may not work.";
141+
log.warn(msg, m.name);
142+
}
143+
candidates.clear();
144+
final var monads = Map.of("single", candidatesSingle, "multiple", candidatesMultiple);
145+
m.vendorExtensions.put("x-monads", monads);
146+
m.vendorExtensions.put("x-is-one-of-interface", true); // enforce template usage
147+
}
148+
}
149+
}
150+
151+
@SuppressWarnings( { "rawtypes", "RedundantSuppression" } )
152+
@Override
153+
protected void updateModelForObject( @Nonnull final CodegenModel m, @Nonnull final Schema schema )
154+
{
155+
// Disable additional attributes to prevent model classes from extending "HashMap"
156+
// SAP Cloud SDK offers custom field APIs to handle additional attributes already
157+
schema.setAdditionalProperties(Boolean.FALSE);
158+
super.updateModelForObject(m, schema);
159+
}
160+
}

datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/GenerationConfigurationConverter.java

Lines changed: 1 addition & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,25 @@
11
package com.sap.cloud.sdk.datamodel.openapi.generator;
22

3-
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.FIX_REDUNDANT_IS_BOOLEAN_PREFIX;
4-
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_FLOAT_ARRAYS;
5-
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_ONE_OF_CREATORS;
6-
73
import java.nio.file.Path;
84
import java.nio.file.Paths;
95
import java.time.Year;
106
import java.util.HashMap;
11-
import java.util.HashSet;
127
import java.util.List;
138
import java.util.Map;
14-
import java.util.Set;
15-
import java.util.regex.Pattern;
169

1710
import javax.annotation.Nonnull;
18-
import javax.annotation.Nullable;
1911

2012
import org.openapitools.codegen.ClientOptInput;
2113
import org.openapitools.codegen.CodegenConstants;
22-
import org.openapitools.codegen.CodegenModel;
23-
import org.openapitools.codegen.CodegenOperation;
24-
import org.openapitools.codegen.CodegenProperty;
2514
import org.openapitools.codegen.config.GlobalSettings;
2615
import org.openapitools.codegen.languages.JavaClientCodegen;
27-
import org.openapitools.codegen.model.ModelMap;
28-
import org.openapitools.codegen.model.OperationsMap;
2916

3017
import com.google.common.base.Strings;
3118
import com.sap.cloud.sdk.datamodel.openapi.generator.model.ApiMaturity;
3219
import com.sap.cloud.sdk.datamodel.openapi.generator.model.GenerationConfiguration;
3320

3421
import io.swagger.parser.OpenAPIParser;
3522
import io.swagger.v3.oas.models.OpenAPI;
36-
import io.swagger.v3.oas.models.media.Schema;
3723
import io.swagger.v3.parser.core.models.AuthorizationValue;
3824
import io.swagger.v3.parser.core.models.ParseOptions;
3925
import lombok.extern.slf4j.Slf4j;
@@ -82,131 +68,7 @@ static ClientOptInput convertGenerationConfiguration(
8268

8369
private static JavaClientCodegen createCodegenConfig( @Nonnull final GenerationConfiguration config )
8470
{
85-
final var primitives = Set.of("String", "Integer", "Long", "Double", "Float", "Byte");
86-
final var doubleIs = Pattern.compile("^isIs[A-Z]").asPredicate();
87-
return new JavaClientCodegen()
88-
{
89-
@SuppressWarnings( { "rawtypes", "RedundantSuppression" } )
90-
@Override
91-
protected void updatePropertyForArray(
92-
@Nonnull final CodegenProperty property,
93-
@Nonnull final CodegenProperty innerProperty )
94-
{
95-
super.updatePropertyForArray(property, innerProperty);
96-
97-
if( USE_FLOAT_ARRAYS.isEnabled(config) && innerProperty.isNumber && property.isArray ) {
98-
property.datatypeWithEnum = "float[]";
99-
property.vendorExtensions.put("isPrimitiveArray", true);
100-
}
101-
}
102-
103-
@SuppressWarnings( { "rawtypes", "RedundantSuppression" } )
104-
@Override
105-
@Nullable
106-
public String toDefaultValue( @Nonnull final CodegenProperty cp, @Nonnull final Schema schema )
107-
{
108-
if( USE_FLOAT_ARRAYS.isEnabled(config) && "float[]".equals(cp.datatypeWithEnum) ) {
109-
return null;
110-
}
111-
return super.toDefaultValue(cp, schema);
112-
}
113-
114-
@Override
115-
@Nullable
116-
public String toBooleanGetter( @Nullable final String name )
117-
{
118-
final String result = super.toBooleanGetter(name);
119-
if( FIX_REDUNDANT_IS_BOOLEAN_PREFIX.isEnabled(config) && result != null && doubleIs.test(result) ) {
120-
return "is" + result.substring(4);
121-
}
122-
return result;
123-
}
124-
125-
// Custom processor to inject "x-return-nullable" extension
126-
@Override
127-
@Nonnull
128-
public OperationsMap postProcessOperationsWithModels(
129-
@Nonnull final OperationsMap ops,
130-
@Nonnull final List<ModelMap> allModels )
131-
{
132-
for( final CodegenOperation op : ops.getOperations().getOperation() ) {
133-
final var noContent =
134-
op.isResponseOptional
135-
|| op.responses == null
136-
|| op.responses.stream().anyMatch(r -> "204".equals(r.code));
137-
op.vendorExtensions.put("x-return-nullable", op.returnType != null && noContent);
138-
}
139-
return super.postProcessOperationsWithModels(ops, allModels);
140-
}
141-
142-
@SuppressWarnings( { "rawtypes", "RedundantSuppression" } )
143-
@Override
144-
protected void updateModelForComposedSchema(
145-
@Nonnull final CodegenModel m,
146-
@Nonnull final Schema schema,
147-
@Nonnull final Map<String, Schema> allDefinitions )
148-
{
149-
super.updateModelForComposedSchema(m, schema, allDefinitions);
150-
151-
if( USE_ONE_OF_CREATORS.isEnabled(config) ) {
152-
useCreatorsForInterfaceSubtypes(m);
153-
}
154-
}
155-
156-
/**
157-
* Use JsonCreator for interface sub-types in case there are any primitives.
158-
*
159-
* @param m
160-
* The model to update.
161-
*/
162-
private void useCreatorsForInterfaceSubtypes( @Nonnull final CodegenModel m )
163-
{
164-
if( m.discriminator != null ) {
165-
return;
166-
}
167-
boolean useCreators = false;
168-
for( final Set<String> candidates : List.of(m.anyOf, m.oneOf) ) {
169-
int nonPrimitives = 0;
170-
final var candidatesSingle = new HashSet<String>();
171-
final var candidatesMultiple = new HashSet<String>();
172-
173-
for( final String candidate : candidates ) {
174-
if( candidate.startsWith("List<") ) {
175-
final var c1 = candidate.substring(5, candidate.length() - 1);
176-
candidatesMultiple.add(c1);
177-
useCreators = true;
178-
} else {
179-
candidatesSingle.add(candidate);
180-
useCreators |= primitives.contains(candidate);
181-
if( !primitives.contains(candidate) ) {
182-
nonPrimitives++;
183-
}
184-
}
185-
}
186-
if( useCreators ) {
187-
if( nonPrimitives > 1 ) {
188-
final var msg =
189-
"Generating interface with mixed multiple non-primitive and primitive sub-types: {}. Deserialization may not work.";
190-
log.warn(msg, m.name);
191-
}
192-
candidates.clear();
193-
final var monads = Map.of("single", candidatesSingle, "multiple", candidatesMultiple);
194-
m.vendorExtensions.put("x-monads", monads);
195-
m.vendorExtensions.put("x-is-one-of-interface", true); // enforce template usage
196-
}
197-
}
198-
}
199-
200-
@SuppressWarnings( { "rawtypes", "RedundantSuppression" } )
201-
@Override
202-
protected void updateModelForObject( @Nonnull final CodegenModel m, @Nonnull final Schema schema )
203-
{
204-
// Disable additional attributes to prevent model classes from extending "HashMap"
205-
// SAP Cloud SDK offers custom field APIs to handle additional attributes already
206-
schema.setAdditionalProperties(Boolean.FALSE);
207-
super.updateModelForObject(m, schema);
208-
}
209-
};
71+
return new CustomJavaClientCodegen(config);
21072
}
21173

21274
private static void setGlobalSettings( @Nonnull final GenerationConfiguration configuration )

0 commit comments

Comments
 (0)