Author: kwin Date: Fri Nov 28 14:50:39 2014 New Revision: 1642306 URL: http://svn.apache.org/r1642306 Log: SLING-4013 allow optional properties/child resources within validation model
Modified: sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ChildResource.java sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ResourceProperty.java sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ChildResourceImpl.java sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/Constants.java sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ResourcePropertyImpl.java sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java sling/trunk/contrib/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java Modified: sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ChildResource.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ChildResource.java?rev=1642306&r1=1642305&r2=1642306&view=diff ============================================================================== --- sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ChildResource.java (original) +++ sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ChildResource.java Fri Nov 28 14:50:39 2014 @@ -44,6 +44,13 @@ public interface ChildResource { Pattern getNamePattern(); /** + * Returns {@code true} if at least one resource matching the name/namePattern is required. + * + * @return {@code true} if the resource is required, {@code false} otherwise + */ + boolean isRequired(); + + /** * Returns the properties this child resource is expected to have. * * @return the properties set Modified: sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ResourceProperty.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ResourceProperty.java?rev=1642306&r1=1642305&r2=1642306&view=diff ============================================================================== --- sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ResourceProperty.java (original) +++ sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ResourceProperty.java Fri Nov 28 14:50:39 2014 @@ -49,6 +49,13 @@ public interface ResourceProperty { boolean isMultiple(); /** + * Returns {@code true} if at least one property matching the name/namePattern is required. + * + * @return {@code true} if the property is required, {@code false} otherwise + */ + boolean isRequired(); + + /** * Returns a list of {@link ParameterizedValidator}s which should be applied on this property. * * @return the list of validators Modified: sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ChildResourceImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ChildResourceImpl.java?rev=1642306&r1=1642305&r2=1642306&view=diff ============================================================================== --- sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ChildResourceImpl.java (original) +++ sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ChildResourceImpl.java Fri Nov 28 14:50:39 2014 @@ -8,6 +8,7 @@ import java.util.regex.PatternSyntaxExce import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.commons.osgi.PropertiesUtil; import org.apache.sling.validation.api.ChildResource; import org.apache.sling.validation.api.ResourceProperty; import org.apache.sling.validation.api.Validator; @@ -22,6 +23,7 @@ public class ChildResourceImpl implement private final Pattern namePattern; private final Set<ResourceProperty> properties; private final List<ChildResource> children; + private final boolean isRequired; public ChildResourceImpl(Resource modelResource, Resource childResource, Map<String, Validator<?>> validatorsMap, List<ChildResource> children) { String root = modelResource.getPath(); @@ -46,7 +48,7 @@ public class ChildResourceImpl implement name = childResource.getName(); namePattern = null; } - + isRequired = !PropertiesUtil.toBoolean(childrenProperties.get(Constants.OPTIONAL), false); properties = JCRBuilder.buildProperties(validatorsMap, childResource.getChild(Constants.PROPERTIES)); this.children = children; } @@ -69,4 +71,9 @@ public class ChildResourceImpl implement public List<ChildResource> getChildren() { return children; } + + @Override + public boolean isRequired() { + return isRequired; + } } Modified: sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/Constants.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/Constants.java?rev=1642306&r1=1642305&r2=1642306&view=diff ============================================================================== --- sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/Constants.java (original) +++ sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/Constants.java Fri Nov 28 14:50:39 2014 @@ -30,6 +30,7 @@ public final class Constants { public static final String PROPERTIES = "properties"; public static final String PROPERTY_TYPE = "propertyType"; public static final String PROPERTY_MULTIPLE = "propertyMultiple"; + public static final String OPTIONAL = "optional"; public static final String VALIDATORS = "validators"; public static final String VALIDATOR_ARGUMENTS = "validatorArguments"; public static final String CHILDREN = "children"; Modified: sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ResourcePropertyImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ResourcePropertyImpl.java?rev=1642306&r1=1642305&r2=1642306&view=diff ============================================================================== --- sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ResourcePropertyImpl.java (original) +++ sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ResourcePropertyImpl.java Fri Nov 28 14:50:39 2014 @@ -28,10 +28,11 @@ public class ResourcePropertyImpl implem private String name; private boolean isMultiple; + private boolean isRequired; private List<ParameterizedValidator> validators; private Pattern namePattern; - public ResourcePropertyImpl(String name, String nameRegex, boolean isMultiple, List<ParameterizedValidator> validators) { + public ResourcePropertyImpl(String name, String nameRegex, boolean isMultiple, boolean isRequired, List<ParameterizedValidator> validators) { if (nameRegex != null) { this.name = null; this.namePattern = Pattern.compile(nameRegex); @@ -40,6 +41,7 @@ public class ResourcePropertyImpl implem this.namePattern = null; } this.isMultiple = isMultiple; + this.isRequired = isRequired; this.validators = validators; } @@ -59,6 +61,11 @@ public class ResourcePropertyImpl implem } @Override + public boolean isRequired() { + return isRequired; + } + + @Override public List<ParameterizedValidator> getValidators() { return validators; } Modified: sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java?rev=1642306&r1=1642305&r2=1642306&view=diff ============================================================================== --- sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java (original) +++ sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java Fri Nov 28 14:50:39 2014 @@ -147,14 +147,14 @@ public class ValidationServiceImpl imple foundMatch = true; } } - if (!foundMatch) { + if (!foundMatch && childResource.isRequired()) { result.addFailureMessage(relativePath + childResource.getNamePattern().pattern(), "Missing required child resource."); } } else { Resource expectedResource = resource.getChild(childResource.getName()); if (expectedResource != null) { validateChildResource(expectedResource, relativePath, childResource, result); - } else { + } else if (childResource.isRequired()) { result.addFailureMessage(relativePath + childResource.getName(), "Missing required child resource."); } } @@ -249,7 +249,7 @@ public class ValidationServiceImpl imple validateValueMap(key, valueMap, relativePath, resourceProperty, result); } } - if (!foundMatch) { + if (!foundMatch && resourceProperty.isRequired()) { result.addFailureMessage(relativePath + resourceProperty.getNamePattern(), "Missing required property."); } } else { @@ -262,7 +262,9 @@ public class ValidationServiceImpl imple private void validateValueMap(String property, ValueMap valueMap, String relativePath, ResourceProperty resourceProperty, ValidationResultImpl result) { Object fieldValues = valueMap.get(property); if (fieldValues == null) { - result.addFailureMessage(relativePath + property, "Missing required property."); + if (resourceProperty.isRequired()) { + result.addFailureMessage(relativePath + property, "Missing required property."); + } return; } List<ParameterizedValidator> validators = resourceProperty.getValidators(); Modified: sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java?rev=1642306&r1=1642305&r2=1642306&view=diff ============================================================================== --- sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java (original) +++ sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java Fri Nov 28 14:50:39 2014 @@ -59,6 +59,7 @@ public class JCRBuilder { String fieldName = property.getName(); ValueMap propertyValueMap = property.adaptTo(ValueMap.class); Boolean propertyMultiple = PropertiesUtil.toBoolean(propertyValueMap.get(Constants.PROPERTY_MULTIPLE), false); + Boolean propertyRequired = !PropertiesUtil.toBoolean(propertyValueMap.get(Constants.OPTIONAL), false); String nameRegex = PropertiesUtil.toString(propertyValueMap.get(Constants.NAME_REGEX), null); Resource validators = property.getChild(Constants.VALIDATORS); List<ParameterizedValidator> parameterizedValidators = new ArrayList<ParameterizedValidator>(); @@ -87,7 +88,7 @@ public class JCRBuilder { parameterizedValidators.add(new ParameterizedValidatorImpl(v, new ValueMapDecorator(validatorArgumentsMap))); } } - ResourceProperty f = new ResourcePropertyImpl(fieldName, nameRegex, propertyMultiple, parameterizedValidators); + ResourceProperty f = new ResourcePropertyImpl(fieldName, nameRegex, propertyMultiple, propertyRequired, parameterizedValidators); properties.add(f); } } Modified: sling/trunk/contrib/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java?rev=1642306&r1=1642305&r2=1642306&view=diff ============================================================================== --- sling/trunk/contrib/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java (original) +++ sling/trunk/contrib/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java Fri Nov 28 14:50:39 2014 @@ -226,6 +226,76 @@ public class ValidationServiceImplTest { } } } + + @Test() + public void testValueMapWithMissingOptionalValue() throws Exception { + validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator()); + + TestProperty property = new TestProperty("field1"); + property.optional = true; + property.addValidator("org.apache.sling.validation.impl.validators.RegexValidator", ""); + + ResourceResolver rr = rrf.getAdministrativeResourceResolver(null); + Resource model1 = null; + try { + if (rr != null) { + model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test", + new String[]{"/apps/validation"}, property); + } + ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource"); + HashMap<String, Object> hashMap = new HashMap<String, Object>() {{ + put("field2", "1"); + }}; + ValueMap map = new ValueMapDecorator(hashMap); + ValidationResult vr = validationService.validate(map, vm); + Assert.assertTrue(vr.isValid()); + } finally { + if (model1 != null) { + rr.delete(model1); + } + if (rr != null) { + rr.commit(); + rr.close(); + } + } + } + + @Test() + public void testValueMapWithEmptyOptionalValue() throws Exception { + validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator()); + + TestProperty property = new TestProperty("field1"); + property.optional = true; + property.addValidator("org.apache.sling.validation.impl.validators.RegexValidator", "regex=abc"); + + ResourceResolver rr = rrf.getAdministrativeResourceResolver(null); + Resource model1 = null; + try { + if (rr != null) { + model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test", + new String[]{"/apps/validation"}, property); + } + ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource"); + HashMap<String, Object> hashMap = new HashMap<String, Object>() {{ + put("field1", ""); + }}; + ValueMap map = new ValueMapDecorator(hashMap); + ValidationResult vr = validationService.validate(map, vm); + Assert.assertFalse(vr.isValid()); + // check for correct error message + Map<String, List<String>> expectedFailureMessages = new HashMap<String, List<String>>(); + expectedFailureMessages.put("field2", Arrays.asList("Property does not match the pattern abc")); + Assert.assertThat(vr.getFailureMessages().entrySet(), Matchers.equalTo(expectedFailureMessages.entrySet())); + } finally { + if (model1 != null) { + rr.delete(model1); + } + if (rr != null) { + rr.commit(); + rr.close(); + } + } + } @Test public void testValueMapWithCorrectDataType() throws Exception { @@ -281,27 +351,23 @@ public class ValidationServiceImplTest { model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test", new String[]{"/apps/validation"}, property); - Resource child = createValidationModelChildResource(model1, "child1", null, new TestProperty("hello")); - createValidationModelChildResource(child, "grandChild1", null, new TestProperty("hello")); + Resource child = createValidationModelChildResource(model1, "child1", null, false, new TestProperty("hello")); + createValidationModelChildResource(child, "grandChild1", null, false, new TestProperty("hello")); testResource = ResourceUtil.getOrCreateResource(rr, "/apps/validation/1/resource", JcrConstants.NT_UNSTRUCTURED, JcrConstants.NT_UNSTRUCTURED, true); ModifiableValueMap mvm = testResource.adaptTo(ModifiableValueMap.class); mvm.put("field1", "1"); - rr.commit(); Resource childResource = rr.create(testResource, "child1", new HashMap<String, Object>(){{ put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED); }}); - rr.commit(); - Resource resourceChild = rr.create(testResource, "child1", new HashMap<String, Object>(){{ put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED); }}); mvm = resourceChild.adaptTo(ModifiableValueMap.class); mvm.put("hello", "1"); - rr.commit(); // /apps/validation/1/resource/child1/grandChild1 will miss its mandatory "hello" property Resource resourceGrandChild = rr.create(resourceChild, "grandChild1", new HashMap<String, Object>(){{ @@ -320,15 +386,54 @@ public class ValidationServiceImplTest { assertThat(vr.getFailureMessages().keySet(), Matchers.hasSize(1)); } finally { if (rr != null) { - if (model1 != null) { - rr.delete(model1); - } - if (testResource != null) { - rr.delete(testResource); - } + rr.delete(model1); + } + if (testResource != null) { + rr.delete(testResource); + } + rr.commit(); + rr.close(); + } + } + + @Test + public void testResourceWithMissingOptionalChildProperty() throws Exception { + validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator()); + + TestProperty property = new TestProperty("field1"); + property.addValidator("org.apache.sling.validation.impl.validators.RegexValidator", RegexValidator.REGEX_PARAM + "=" + "\\d"); + ResourceResolver rr = rrf.getAdministrativeResourceResolver(null); + Resource model1 = null; + Resource testResource = null; + try { + if (rr != null) { + model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test", + new String[]{"/apps/validation"}, property); + + createValidationModelChildResource(model1, "child1", null, true, new TestProperty("hello")); + + testResource = ResourceUtil.getOrCreateResource(rr, "/apps/validation/1/resource", JcrConstants.NT_UNSTRUCTURED, + JcrConstants.NT_UNSTRUCTURED, true); + ModifiableValueMap mvm = testResource.adaptTo(ModifiableValueMap.class); + mvm.put("field1", "1"); + + rr.create(testResource, "child2", new HashMap<String, Object>(){{ + put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED); + }}); rr.commit(); - rr.close(); } + ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource"); + ValidationResult vr = validationService.validate(testResource, vm); + assertTrue(vr.isValid()); + } finally { + if (rr != null) { + rr.delete(model1); + } + if (testResource != null) { + rr.delete(testResource); + } + rr.commit(); + rr.close(); } } @@ -346,8 +451,8 @@ public class ValidationServiceImplTest { model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test", new String[]{"/apps/validation"}, property); - Resource child = createValidationModelChildResource(model1, "child1", null, new TestProperty("hello")); - createValidationModelChildResource(child, "grandChild1", null, new TestProperty("hello")); + Resource child = createValidationModelChildResource(model1, "child1", null, false, new TestProperty("hello")); + createValidationModelChildResource(child, "grandChild1", null, false, new TestProperty("hello")); testResource = ResourceUtil.getOrCreateResource(rr, "/apps/validation/1/resource", JcrConstants.NT_UNSTRUCTURED, JcrConstants.NT_UNSTRUCTURED, true); @@ -401,8 +506,8 @@ public class ValidationServiceImplTest { if (rr != null) { model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test", new String[]{"/apps/validation"}, property); - Resource child = createValidationModelChildResource(model1, "child1", "child.*", new TestProperty("hello")); - createValidationModelChildResource(child, "grandChild", "grandChild.*", new TestProperty("hello")); + Resource child = createValidationModelChildResource(model1, "child1", "child.*", false, new TestProperty("hello")); + createValidationModelChildResource(child, "grandChild", "grandChild.*", false, new TestProperty("hello")); rr.commit(); testResource = ResourceUtil.getOrCreateResource(rr, "/apps/validation/1/resource", JcrConstants.NT_UNSTRUCTURED, @@ -560,10 +665,12 @@ public class ValidationServiceImplTest { Resource propertyResource = ResourceUtil.getOrCreateResource(rr, propertiesResource.getPath() + "/" + property.name, modelPropertyJCRProperties, null, true); if (propertyResource != null) { + ModifiableValueMap values = propertyResource.adaptTo(ModifiableValueMap.class); if (property.nameRegex != null) { - ModifiableValueMap values = propertyResource.adaptTo(ModifiableValueMap.class); values.put(Constants.NAME_REGEX, property.nameRegex); } + values.put(Constants.PROPERTY_MULTIPLE, property.multiple); + values.put(Constants.OPTIONAL, property.optional); Resource validators = ResourceUtil.getOrCreateResource(rr, propertyResource.getPath() + "/" + Constants.VALIDATORS, JcrConstants.NT_UNSTRUCTURED, null, true); @@ -583,7 +690,7 @@ public class ValidationServiceImplTest { } } - private Resource createValidationModelChildResource(Resource parentResource, String name, String nameRegex, TestProperty... properties) throws PersistenceException { + private Resource createValidationModelChildResource(Resource parentResource, String name, String nameRegex, boolean isOptional, TestProperty... properties) throws PersistenceException { ResourceResolver rr = parentResource.getResourceResolver(); Resource modelChildren = rr.create(parentResource, Constants.CHILDREN, new HashMap<String, Object>(){{ put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED); @@ -591,10 +698,11 @@ public class ValidationServiceImplTest { Resource child = rr.create(modelChildren, name, new HashMap<String, Object>(){{ put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED); }}); + ModifiableValueMap mvm = child.adaptTo(ModifiableValueMap.class); if (nameRegex != null) { - ModifiableValueMap mvm = child.adaptTo(ModifiableValueMap.class); mvm.put(Constants.NAME_REGEX, nameRegex); } + mvm.put(Constants.OPTIONAL, isOptional); createValidationModelProperties(child, properties); return child; } @@ -613,6 +721,8 @@ public class ValidationServiceImplTest { } private class TestProperty { + public boolean optional; + public boolean multiple; final String name; String nameRegex; final Map<String, String[]> validators; @@ -621,6 +731,8 @@ public class ValidationServiceImplTest { validators = new HashMap<String, String[]>(); this.name = name; this.nameRegex = null; + this.optional = false; + this.multiple = false; } TestProperty setNameRegex(String nameRegex) {