diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ValidRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ValidRule.java index be09f8ad0..64bd4f566 100644 --- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ValidRule.java +++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ValidRule.java @@ -46,15 +46,34 @@ public ValidRule(RuleFactory ruleFactory) { @Override public JType apply(String nodeName, JsonNode node, JsonNode parent, JType type, Schema currentSchema) { + if (ruleFactory.getGenerationConfig().isIncludeJsr303Annotations() && type instanceof JClass jclass) { + if (!isContainer(jclass) && !isScalar(jclass)) { + return JAnnotatedClass.of(jclass).annotated(getValidClass()); + } + if (null != node && node.has("existingJavaType")) { + return applyToExistingJavaType(jclass); + } + } + return type; + } - if (ruleFactory.getGenerationConfig().isIncludeJsr303Annotations() - && type instanceof JClass jclass - && !isContainer(jclass) - && !isScalar(jclass)) { - return JAnnotatedClass.of(jclass).annotated(getValidClass()); - } else { - return type; + private JClass applyToExistingJavaType(JClass jClass) { + if (jClass.isReference() && isContainer(jClass.erasure())) { + final var typeParameters = jClass.getTypeParameters(); + if ((jClass.owner().ref(Iterable.class).isAssignableFrom(jClass.erasure()) + || jClass.owner().ref(Optional.class).isAssignableFrom(jClass.erasure())) + && typeParameters.size() == 1 + && !isScalar(typeParameters.get(0))) { + return jClass.erasure().narrow(applyToExistingJavaType(typeParameters.get(0))); + } else if (jClass.owner().ref(Map.class).isAssignableFrom(jClass.erasure()) + && typeParameters.size() == 2 && !isScalar(typeParameters.get(1))) { + return jClass.erasure().narrow(typeParameters.get(0), applyToExistingJavaType(typeParameters.get(1))); + } + } + if (!isContainer(jClass) && !isScalar(jClass)) { + return JAnnotatedClass.of(jClass).annotated(getValidClass()); } + return jClass; } private boolean isContainer(JClass jclass) { diff --git a/jsonschema2pojo-integration-tests/src/test/java/com/example/ClassWithSizeAnnotation.java b/jsonschema2pojo-integration-tests/src/test/java/com/example/ClassWithSizeAnnotation.java new file mode 100644 index 000000000..1e746aaee --- /dev/null +++ b/jsonschema2pojo-integration-tests/src/test/java/com/example/ClassWithSizeAnnotation.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2010-2020 Nokia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example; + +import jakarta.validation.constraints.Size; + +public class ClassWithSizeAnnotation { + + @Size(max = 2) + @javax.validation.constraints.Size(max = 2) + private final String text; + + public ClassWithSizeAnnotation() { + text = "should fail"; + } + + public ClassWithSizeAnnotation(String text) { + this.text = text; + } + +} diff --git a/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/config/IncludeJsr303AnnotationsIT.java b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/config/IncludeJsr303AnnotationsIT.java index 984df4981..6e8434ea0 100644 --- a/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/config/IncludeJsr303AnnotationsIT.java +++ b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/config/IncludeJsr303AnnotationsIT.java @@ -38,6 +38,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import org.apache.bval.jsr.ApacheValidationProvider; @@ -47,6 +49,8 @@ import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.ValueSource; +import com.example.ClassWithSizeAnnotation; + @SuppressWarnings("rawtypes") @ParameterizedClass @ValueSource(booleans = { true, false }) @@ -631,7 +635,7 @@ public void jsr303ValidAnnotationOnAdditionalPropertiesWithItemTypeArray() throw } @Test - public void jsr303ValidAnnotationsOnExistingJavaType() throws Exception { + public void jsr303ValidAnnotationsOnExistingJavaTypeWithScalar() throws Exception { ClassLoader resultsClassLoader = schemaRule.generateAndCompile( "/schema/jsr303/validExistingJavaType.json", "com.example", config("includeJsr303Annotations", true, "useJakartaValidation", useJakartaValidation)); @@ -700,6 +704,37 @@ public void jsr303ValidAnnotationNotAppliedToScalarTypes() throws Exception { source, not(containsString("@Valid"))); } + @Test + void jsr303AnnotationsValidatedForExistingJavaType() throws ReflectiveOperationException { + ClassLoader classLoader = schemaRule.generateAndCompile( + "/schema/jsr303/existingJavaTypeProperties.json", + "com.example", + config("includeJsr303Annotations", true, "useJakartaValidation", useJakartaValidation)); + + Class clazz = classLoader.loadClass("com.example.ExistingJavaTypeProperties"); + Object instance = clazz.getDeclaredConstructor().newInstance(); + + assertNumberOfConstraintViolationsOn(instance, is(0)); + + clazz.getDeclaredMethod("setMapOfStringOptionalListOfExistingClasses", Map.class) + .invoke(instance, Map.of("a", Optional.of(List.of(new ClassWithSizeAnnotation())))); + assertNumberOfConstraintViolationsOn(instance, is(1)); + + clazz.getDeclaredMethod("setListOfOptionalStringExistingClassMaps", List.class) + .invoke(instance, List.of(Optional.of(Map.of("a", new ClassWithSizeAnnotation())))); + assertNumberOfConstraintViolationsOn(instance, is(2)); + + clazz.getDeclaredMethod("setListOfOptionalStringObjectMaps", List.class) + .invoke(instance, List.of(Optional.of(Map.of("a", new ClassWithSizeAnnotation())))); + assertNumberOfConstraintViolationsOn(instance, is(3)); + + clazz.getDeclaredMethod("setOptionalOfExistingClass", Optional.class).invoke(instance, Optional.of(new ClassWithSizeAnnotation())); + assertNumberOfConstraintViolationsOn(instance, is(4)); + + clazz.getDeclaredMethod("setMapOfStringExistingJavaClasses", Map.class).invoke(instance, Map.of("a", new ClassWithSizeAnnotation())); + assertNumberOfConstraintViolationsOn(instance, is(5)); + } + private void assertNumberOfConstraintViolationsOn(Object instance, Matcher matcher) { final Set violationsForValidInstance = useJakartaValidation ? validator.validate(instance) : javaxValidator.validate(instance); final String validatorName = useJakartaValidation ? "jakarta/hibernate validator" : "javax/bval validator"; diff --git a/jsonschema2pojo-integration-tests/src/test/resources/schema/jsr303/existingJavaTypeProperties.json b/jsonschema2pojo-integration-tests/src/test/resources/schema/jsr303/existingJavaTypeProperties.json new file mode 100644 index 000000000..0692cc990 --- /dev/null +++ b/jsonschema2pojo-integration-tests/src/test/resources/schema/jsr303/existingJavaTypeProperties.json @@ -0,0 +1,46 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "mapOfStringOptionalListOfExistingClasses": { + "type": "object", + "existingJavaType": "java.util.Map>>" + }, + "listOfOptionalStringExistingClassMaps": { + "type": "object", + "existingJavaType": "java.util.List>>" + }, + "listOfOptionalStringObjectMaps": { + "type": "object", + "existingJavaType": "java.util.List>>" + }, + "listOfOptionalStringStringMaps": { + "type": "object", + "existingJavaType": "java.util.List>>" + }, + "optionalOfExistingClass": { + "type": "object", + "existingJavaType": "java.util.Optional" + }, + "mapOfStringExistingJavaClasses": { + "type": "object", + "existingJavaType": "java.util.Map" + }, + "listOfObjects": { + "type": "object", + "existingJavaType": "java.util.List" + }, + "mapOfStringObject": { + "type": "object", + "existingJavaType": "java.util.Map" + }, + "rawOptional": { + "type": "object", + "existingJavaType": "java.util.Optional" + }, + "rawMap": { + "type": "object", + "existingJavaType": "java.util.Map" + } + } +} diff --git a/jsonschema2pojo-integration-tests/src/test/resources/schema/jsr303/minAndMaxItemsExistingType.json b/jsonschema2pojo-integration-tests/src/test/resources/schema/jsr303/minAndMaxItemsExistingType.json index 835b42ff9..fca223283 100644 --- a/jsonschema2pojo-integration-tests/src/test/resources/schema/jsr303/minAndMaxItemsExistingType.json +++ b/jsonschema2pojo-integration-tests/src/test/resources/schema/jsr303/minAndMaxItemsExistingType.json @@ -5,7 +5,7 @@ "type" : "array", "minItems" : 2, "maxItems" : 4, - "existingJavaType" : "java.util.LinkedList" + "existingJavaType" : "java.util.LinkedList" } } }