Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DATAMONGO-36 - Validation support to MongoTemplate #2

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 26 additions & 11 deletions spring-data-mongodb/pom.xml
@@ -1,6 +1,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
Expand Down Expand Up @@ -42,9 +42,9 @@
<version>${org.springframework.version.range}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
Expand Down Expand Up @@ -99,7 +99,7 @@
<version>1.0</version>
<optional>true</optional>
</dependency>

<!-- CDI -->
<dependency>
<groupId>javax.enterprise</groupId>
Expand All @@ -108,30 +108,45 @@
<scope>provided</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>${cdi.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.openwebbeans.test</groupId>
<artifactId>cditest-owb</artifactId>
<version>${webbeans.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>test</scope>
</dependency>

<!-- JSR 303 Validation -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
<scope>test</scope>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
Expand All @@ -158,5 +173,5 @@
</plugin>
</plugins>
</build>

</project>
Expand Up @@ -25,4 +25,5 @@ public abstract class BeanNames {
static final String INDEX_HELPER = "indexCreationHelper";
static final String MONGO = "mongo";
static final String DB_FACTORY = "mongoDbFactory";
static final String VALIDATING_EVENT_LISTENER = "validatingMongoEventListener";
}
Expand Up @@ -30,12 +30,8 @@
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.*;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
Expand All @@ -52,18 +48,23 @@
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
import org.springframework.scheduling.config.AnnotationDrivenBeanDefinitionParser;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.w3c.dom.Element;

/**
* @author Jon Brisbin <jbrisbin@vmware.com>
* @author Oliver Gierke
* @author Maciej Walkowiak <walkowiak.maciej@yahoo.com>
*/
public class MappingMongoConverterParser extends AbstractBeanDefinitionParser {

private static final String BASE_PACKAGE = "base-package";
private static final boolean jsr303Present = ClassUtils.isPresent("javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
Expand Down Expand Up @@ -107,9 +108,50 @@ protected AbstractBeanDefinition parseInternal(Element element, ParserContext pa
registry.registerBeanDefinition(INDEX_HELPER, indexHelperBuilder.getBeanDefinition());
}

BeanDefinition validatingMongoEventListener = potentiallyCreateValidatingMongoEventListener(element, parserContext);

if (validatingMongoEventListener != null) {
registry.registerBeanDefinition(VALIDATING_EVENT_LISTENER, validatingMongoEventListener);
}

return converterBuilder.getBeanDefinition();
}

private BeanDefinition potentiallyCreateValidatingMongoEventListener(Element element, ParserContext parserContext) {
String disableValidation = element.getAttribute("disable-validation");

BeanDefinition result = null;

if (disableValidation == null || Boolean.valueOf(disableValidation) == Boolean.FALSE) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();

RuntimeBeanReference validator = getValidator(builder, parserContext);

if (validator != null) {
builder.getRawBeanDefinition().setBeanClass(ValidatingMongoEventListener.class);
builder.addConstructorArgValue(validator);

result = builder.getBeanDefinition();
}
}

return result;
}

private RuntimeBeanReference getValidator(Object source, ParserContext parserContext) {
if (!jsr303Present) {
return null;
}

RootBeanDefinition validatorDef = new RootBeanDefinition(LocalValidatorFactoryBean.class);
validatorDef.setSource(source);
validatorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String validatorName = parserContext.getReaderContext().registerWithGeneratedName(validatorDef);
parserContext.registerComponent(new BeanComponentDefinition(validatorDef, validatorName));

return new RuntimeBeanReference(validatorName);
}

private String potentiallyCreateMappingContext(Element element, ParserContext parserContext,
BeanDefinition conversionsDefinition) {

Expand Down Expand Up @@ -221,7 +263,7 @@ public BeanMetadataElement parseConverter(Element element, ParserContext parserC

/**
* {@link TypeFilter} that returns {@literal false} in case any of the given delegates matches.
*
*
* @author Oliver Gierke
*/
private static class NegatingFilter implements TypeFilter {
Expand All @@ -230,7 +272,7 @@ private static class NegatingFilter implements TypeFilter {

/**
* Creates a new {@link NegatingFilter} with the given delegates.
*
*
* @param filters
*/
public NegatingFilter(TypeFilter... filters) {
Expand Down
@@ -0,0 +1,54 @@
/*
* Copyright 2010 the original author or authors.
*
* 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 org.springframework.data.mongodb.core.mapping.event;

import com.mongodb.DBObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.util.Set;

/**
* javax.validation dependant entities validator.
* When it is registered as Spring component its automatically invoked before entities are saved in database.
*
* @author Maciej Walkowiak <walkowiak.maciej@yahoo.com>
*/
public class ValidatingMongoEventListener extends AbstractMongoEventListener {
private static final Logger LOG = LoggerFactory.getLogger(ValidatingMongoEventListener.class);

private final Validator validator;

public ValidatingMongoEventListener(Validator validator) {
this.validator = validator;
}

@Override
public void onBeforeSave(Object source, DBObject dbo) {
LOG.debug("Validating object: {}", source);

Set violations = validator.validate(source);

if (violations.size() > 0) {
LOG.info("During object: {} validation violations found: {}", source, violations);

throw new ConstraintViolationException((Set<ConstraintViolation<?>>) violations);
}
}
}
Expand Up @@ -214,6 +214,13 @@ The base package in which to scan for entities annotated with @Document
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="disable-validation" type="xsd:boolean" use="optional">
<xsd:annotation>
<xsd:documentation source="org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener">
Disables JSR-303 validation on MongoDB documents before they are saved. By default it is set to false.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>

Expand Down
Expand Up @@ -37,7 +37,7 @@
import com.mongodb.DBObject;

/**
* Integration tests for {@link MongoParser}.
* Integration tests for {@link MappingMongoConverterParser}.
*
* @author Oliver Gierke
*/
Expand Down
@@ -0,0 +1,65 @@
/*
* Copyright 2010 the original author or authors.
*
* 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 org.springframework.data.mongodb.config;

import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;

/**
* Integration test for creation of instance of {@link org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener}
* by defining <code>&lt;mongo:mapping-converter /&gt;</code> in context XML
*
* @author Maciej Walkowiak <walkowiak.maciej@yahoo.com>
*/
public class MappingMongoConverterParserValidationIntegrationTests {
private DefaultListableBeanFactory factory;
private BeanDefinitionReader reader;

@Before
public void setUp() {
factory = new DefaultListableBeanFactory();
reader = new XmlBeanDefinitionReader(factory);
}

@Test
public void validatingEventListenerCreatedWithDefaultConfig() {
reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-default.xml"));

assertThat(factory.getBean(BeanNames.VALIDATING_EVENT_LISTENER), is(not(nullValue())));
}

@Test
public void validatingEventListenerCreatedWhenValidationEnabled() {
reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-validation-enabled.xml"));

assertThat(factory.getBean(BeanNames.VALIDATING_EVENT_LISTENER), is(not(nullValue())));
}

@Test(expected = NoSuchBeanDefinitionException.class)
public void validatingEventListenersIsNotCreatedWhenDisabled() {
reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-validation-disabled.xml"));

factory.getBean(BeanNames.VALIDATING_EVENT_LISTENER);
}
}
@@ -0,0 +1,30 @@
package org.springframework.data.mongodb.core.mapping.event;

import javax.validation.constraints.Min;
import javax.validation.constraints.Size;

/**
* Class used to test JSR-303 validation @{link org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener}
*
* @author Maciej Walkowiak
*/
public class User {
@Size(min = 10)
private String name;

@Min(18)
private Integer age;

public User(String name, Integer age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public Integer getAge() {
return age;
}
}