diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index ad546153e5..094d9a9a71 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -1,6 +1,6 @@
+ 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">
4.0.0
org.springframework.data
@@ -42,9 +42,9 @@
${org.springframework.version.range}
- commons-logging
- commons-logging
-
+ commons-logging
+ commons-logging
+
@@ -99,7 +99,7 @@
1.0
true
-
+
javax.enterprise
@@ -108,21 +108,21 @@
provided
true
-
+
javax.el
el-api
${cdi.version}
test
-
+
org.apache.openwebbeans.test
cditest-owb
${webbeans.version}
test
-
+
javax.servlet
servlet-api
@@ -130,8 +130,23 @@
test
+
+
+ javax.validation
+ validation-api
+ 1.0.0.GA
+ compile
+
+
+
+ org.hibernate
+ hibernate-validator
+ 4.2.0.Final
+ test
+
+
-
+
@@ -158,5 +173,5 @@
-
+
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/BeanNames.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/BeanNames.java
index c1d21bf094..2c4ef69d53 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/BeanNames.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/BeanNames.java
@@ -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";
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java
index 5e007e34ce..6fe7c1af0b 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java
@@ -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;
@@ -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
* @author Oliver Gierke
+ * @author Maciej Walkowiak
*/
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)
@@ -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) {
@@ -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 {
@@ -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) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListener.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListener.java
new file mode 100644
index 0000000000..5f9f8d817b
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListener.java
@@ -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
+ */
+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>) violations);
+ }
+ }
+}
diff --git a/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.0.xsd b/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.0.xsd
index 702c855b03..c706ba8b9c 100644
--- a/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.0.xsd
+++ b/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.0.xsd
@@ -214,6 +214,13 @@ The base package in which to scan for entities annotated with @Document
+
+
+
+ Disables JSR-303 validation on MongoDB documents before they are saved. By default it is set to false.
+
+
+
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java
index 533b6ce973..65b078d59c 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java
@@ -37,7 +37,7 @@
import com.mongodb.DBObject;
/**
- * Integration tests for {@link MongoParser}.
+ * Integration tests for {@link MappingMongoConverterParser}.
*
* @author Oliver Gierke
*/
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserValidationIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserValidationIntegrationTests.java
new file mode 100644
index 0000000000..13eaddc6dc
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserValidationIntegrationTests.java
@@ -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 <mongo:mapping-converter />
in context XML
+ *
+ * @author Maciej Walkowiak
+ */
+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);
+ }
+}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/User.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/User.java
new file mode 100644
index 0000000000..099d5001cc
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/User.java
@@ -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;
+ }
+}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTest.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTest.java
new file mode 100644
index 0000000000..f35ccb8b9d
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTest.java
@@ -0,0 +1,46 @@
+package org.springframework.data.mongodb.core.mapping.event;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import javax.validation.ConstraintViolationException;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class ValidatingMongoEventListenerTest {
+ @Autowired
+ private MongoTemplate mongoTemplate;
+
+ @Test
+ public void shouldThrowConstraintViolationException() {
+ //given
+ User user = new User("john", 17);
+
+ try {
+ //when
+ mongoTemplate.save(user);
+
+ //then
+ fail();
+ } catch (ConstraintViolationException e) {
+ assertThat(e.getConstraintViolations().size(), equalTo(2));
+ }
+ }
+
+ @Test
+ public void shouldNotThrowAnyExceptions() {
+ //given
+ User user = new User("john smith", 18);
+
+ //when & then
+ mongoTemplate.save(user);
+ }
+}
diff --git a/spring-data-mongodb/src/test/resources/namespace/converter-default.xml b/spring-data-mongodb/src/test/resources/namespace/converter-default.xml
new file mode 100644
index 0000000000..76a4cb1b1e
--- /dev/null
+++ b/spring-data-mongodb/src/test/resources/namespace/converter-default.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/spring-data-mongodb/src/test/resources/namespace/converter-validation-disabled.xml b/spring-data-mongodb/src/test/resources/namespace/converter-validation-disabled.xml
new file mode 100644
index 0000000000..a8035d5719
--- /dev/null
+++ b/spring-data-mongodb/src/test/resources/namespace/converter-validation-disabled.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/spring-data-mongodb/src/test/resources/namespace/converter-validation-enabled.xml b/spring-data-mongodb/src/test/resources/namespace/converter-validation-enabled.xml
new file mode 100644
index 0000000000..0c97b7f7d3
--- /dev/null
+++ b/spring-data-mongodb/src/test/resources/namespace/converter-validation-enabled.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTest-context.xml b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTest-context.xml
new file mode 100644
index 0000000000..1e54713b16
--- /dev/null
+++ b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTest-context.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-data-mongodb/template.mf b/spring-data-mongodb/template.mf
index ae1431b311..c0ead7cc56 100644
--- a/spring-data-mongodb/template.mf
+++ b/spring-data-mongodb/template.mf
@@ -10,6 +10,7 @@ Import-Template:
com.mongodb.*;version="0",
com.mysema.query.*;version="[2.1.1, 3.0.0)";resolution:=optional,
javax.annotation.processing.*;version="0",
+ javax.validation.*;version="1.0.0.GA",
javax.enterprise.*;version="${cdi.version:[=.=.=,+1.0.0)}";resolution:=optional,
javax.tools.*;version="0",
org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional,