From bef4d52ace554236b86c437fd9ec72a79350b11b Mon Sep 17 00:00:00 2001 From: Michael Nitschinger Date: Mon, 10 Mar 2014 13:07:05 +0100 Subject: [PATCH] DATACOUCH-71 - Add support for JSR 303 validation. This changeset adds validation support for entities, and since this depends on events, the event functionality has been added as well. Now also generic event listeners can be attached. --- pom.xml | 16 +++ .../couchbase/core/CouchbaseTemplate.java | 47 +++++++-- .../event/AbstractCouchbaseEventListener.java | 97 +++++++++++++++++++ .../core/mapping/event/AfterDeleteEvent.java | 28 ++++++ .../core/mapping/event/AfterSaveEvent.java | 30 ++++++ .../mapping/event/BeforeConvertEvent.java | 28 ++++++ .../core/mapping/event/BeforeDeleteEvent.java | 28 ++++++ .../core/mapping/event/BeforeSaveEvent.java | 30 ++++++ .../mapping/event/CouchbaseMappingEvent.java | 45 +++++++++ .../mapping/event/LoggingEventListener.java | 58 +++++++++++ .../ValidatingCouchbaseEventListener.java | 65 +++++++++++++ .../AbstractCouchbaseEventListenerTests.java | 54 +++++++++++ .../event/EventContextConfiguration.java | 45 +++++++++ .../event/SimpleMappingEventListener.java | 47 +++++++++ .../couchbase/core/mapping/event/User.java | 52 ++++++++++ ...ValidatingCouchbaseEventListenerTests.java | 58 +++++++++++ template.mf | 3 +- 17 files changed, 724 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListener.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterDeleteEvent.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveEvent.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertEvent.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeDeleteEvent.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveEvent.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/CouchbaseMappingEvent.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/LoggingEventListener.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java create mode 100644 src/test/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListenerTests.java create mode 100644 src/test/java/org/springframework/data/couchbase/core/mapping/event/EventContextConfiguration.java create mode 100644 src/test/java/org/springframework/data/couchbase/core/mapping/event/SimpleMappingEventListener.java create mode 100644 src/test/java/org/springframework/data/couchbase/core/mapping/event/User.java create mode 100644 src/test/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListenerTests.java diff --git a/pom.xml b/pom.xml index 47999643..7c56738c 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,7 @@ 1.3.1 2.3.1 1.6.3.RELEASE + 1.0.0.GA @@ -74,6 +75,13 @@ test + + org.hibernate + hibernate-validator + 4.2.0.Final + test + + org.apache.httpcomponents httpclient @@ -94,6 +102,14 @@ true + + + javax.validation + validation-api + ${validation} + true + + org.hamcrest hamcrest-all diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java index 431faa6e..e8631d6f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java @@ -28,6 +28,8 @@ import net.spy.memcached.ReplicateTo; import net.spy.memcached.internal.OperationFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.dao.QueryTimeoutException; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; @@ -39,6 +41,12 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.core.mapping.CouchbaseStorable; +import org.springframework.data.couchbase.core.mapping.event.AfterDeleteEvent; +import org.springframework.data.couchbase.core.mapping.event.AfterSaveEvent; +import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent; +import org.springframework.data.couchbase.core.mapping.event.BeforeDeleteEvent; +import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent; +import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.BeanWrapper; @@ -56,7 +64,7 @@ import java.util.concurrent.TimeoutException; /** * @author Michael Nitschinger */ -public class CouchbaseTemplate implements CouchbaseOperations { +public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventPublisherAware { private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTemplate.class); private static final Collection ITERABLE_CLASSES; @@ -66,6 +74,7 @@ public class CouchbaseTemplate implements CouchbaseOperations { protected final MappingContext, CouchbasePersistentProperty> mappingContext; private final CouchbaseExceptionTranslator exceptionTranslator = new CouchbaseExceptionTranslator(); private final TranslationService translationService; + private ApplicationEventPublisher eventPublisher; private CouchbaseConverter couchbaseConverter; private WriteResultChecking writeResultChecking = DEFAULT_WRITE_RESULT_CHECKING; @@ -86,6 +95,10 @@ public class CouchbaseTemplate implements CouchbaseOperations { this(client, null, null); } + public CouchbaseTemplate(final CouchbaseClient client, final TranslationService translationService) { + this(client, null, translationService); + } + public CouchbaseTemplate(final CouchbaseClient client, final CouchbaseConverter couchbaseConverter, final TranslationService translationService) { this.client = client; this.couchbaseConverter = couchbaseConverter == null ? getDefaultConverter() : couchbaseConverter; @@ -93,11 +106,9 @@ public class CouchbaseTemplate implements CouchbaseOperations { mappingContext = this.couchbaseConverter.getMappingContext(); } - public CouchbaseTemplate(final CouchbaseClient client, final TranslationService translationService) { - this.client = client; - couchbaseConverter = couchbaseConverter == null ? getDefaultConverter() : couchbaseConverter; - this.translationService = translationService == null ? getDefaultTranslationService() : translationService; - mappingContext = couchbaseConverter.getMappingContext(); + @Override + public void setApplicationEventPublisher(final ApplicationEventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; } private static CouchbaseConverter getDefaultConverter() { @@ -282,9 +293,11 @@ public class CouchbaseTemplate implements CouchbaseOperations { final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty(); final Long version = versionProperty != null ? beanWrapper.getProperty(versionProperty, Long.class, true) : null; + maybeEmitEvent(new BeforeConvertEvent(objectToSave)); final CouchbaseDocument converted = new CouchbaseDocument(); couchbaseConverter.write(objectToSave, converted); + maybeEmitEvent(new BeforeSaveEvent(objectToSave, converted)); execute(new BucketCallback() { @Override public Boolean doInBucket() throws InterruptedException, ExecutionException { @@ -310,6 +323,7 @@ public class CouchbaseTemplate implements CouchbaseOperations { } } }); + maybeEmitEvent(new AfterSaveEvent(objectToSave, converted)); } @Override @@ -334,9 +348,11 @@ public class CouchbaseTemplate implements CouchbaseOperations { } } + maybeEmitEvent(new BeforeConvertEvent(objectToInsert)); final CouchbaseDocument converted = new CouchbaseDocument(); couchbaseConverter.write(objectToInsert, converted); + maybeEmitEvent(new BeforeSaveEvent(objectToInsert, converted)); execute(new BucketCallback() { @Override public Boolean doInBucket() throws InterruptedException, ExecutionException { @@ -354,6 +370,7 @@ public class CouchbaseTemplate implements CouchbaseOperations { return result; } }); + maybeEmitEvent(new AfterSaveEvent(objectToInsert, converted)); } @Override @@ -373,9 +390,11 @@ public class CouchbaseTemplate implements CouchbaseOperations { final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty(); final Long version = versionProperty != null ? beanWrapper.getProperty(versionProperty, Long.class, true) : null; + maybeEmitEvent(new BeforeConvertEvent(objectToUpdate)); final CouchbaseDocument converted = new CouchbaseDocument(); couchbaseConverter.write(objectToUpdate, converted); + maybeEmitEvent(new BeforeSaveEvent(objectToUpdate, converted)); execute(new BucketCallback() { @Override public Boolean doInBucket() throws InterruptedException, ExecutionException { @@ -397,6 +416,7 @@ public class CouchbaseTemplate implements CouchbaseOperations { } } }); + maybeEmitEvent(new AfterSaveEvent(objectToUpdate, converted)); } @Override @@ -410,6 +430,7 @@ public class CouchbaseTemplate implements CouchbaseOperations { public void remove(final Object objectToRemove, final PersistTo persistTo, final ReplicateTo replicateTo) { ensureNotIterable(objectToRemove); + maybeEmitEvent(new BeforeDeleteEvent(objectToRemove)); if (objectToRemove instanceof String) { execute(new BucketCallback() { @Override @@ -417,6 +438,7 @@ public class CouchbaseTemplate implements CouchbaseOperations { return client.delete((String) objectToRemove, persistTo, replicateTo).get(); } }); + maybeEmitEvent(new AfterDeleteEvent(objectToRemove)); return; } @@ -429,6 +451,7 @@ public class CouchbaseTemplate implements CouchbaseOperations { return client.delete(converted.getId()); } }); + maybeEmitEvent(new AfterDeleteEvent(objectToRemove)); } @Override @@ -455,4 +478,16 @@ public class CouchbaseTemplate implements CouchbaseOperations { } } + /** + * Helper method to publish an event if the event publisher is set. + * + * @param event the event to emit. + * @param the enclosed type. + */ + protected void maybeEmitEvent(final CouchbaseMappingEvent event) { + if (eventPublisher != null) { + eventPublisher.publishEvent(event); + } + } + } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListener.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListener.java new file mode 100644 index 00000000..7aea349b --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListener.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.core.GenericTypeResolver; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; + +/** + * Base class to implement domain class specific {@link ApplicationListener}s. + * + * @author Jon Brisbin + * @author Oliver Gierke + * @author Martin Baumgartner + * @author Michael Nitschinger + */ +public class AbstractCouchbaseEventListener implements ApplicationListener> { + private static final Logger LOG = LoggerFactory.getLogger(AbstractCouchbaseEventListener.class); + private final Class domainClass; + + public AbstractCouchbaseEventListener() { + Class typeArgument = GenericTypeResolver.resolveTypeArgument(getClass(), AbstractCouchbaseEventListener.class); + domainClass = typeArgument == null ? Object.class : typeArgument; + } + + @SuppressWarnings("rawtypes") + public void onApplicationEvent(CouchbaseMappingEvent event) { + if (event instanceof BeforeDeleteEvent) { + onBeforeDelete(event.getDocument()); + return; + } else if (event instanceof AfterDeleteEvent) { + onAfterDelete(event.getDocument()); + return; + } + + E source = (E) event.getSource(); + // Check for matching domain type and invoke callbacks + if (source != null && !domainClass.isAssignableFrom(source.getClass())) { + return; + } + + if (event instanceof BeforeConvertEvent) { + onBeforeConvert(source); + } else if (event instanceof BeforeSaveEvent) { + onBeforeSave(source, event.getDocument()); + } else if (event instanceof AfterSaveEvent) { + onAfterSave(source, event.getDocument()); + } + } + + public void onBeforeConvert(E source) { + if (LOG.isDebugEnabled()) { + LOG.debug("onBeforeConvert({})", source); + } + } + + public void onBeforeSave(E source, CouchbaseDocument doc) { + if (LOG.isDebugEnabled()) { + LOG.debug("onBeforeSave({}, {})", source, doc); + } + } + + public void onAfterSave(E source, CouchbaseDocument doc) { + if (LOG.isDebugEnabled()) { + LOG.debug("onAfterSave({}, {})", source, doc); + } + } + + public void onAfterDelete(CouchbaseDocument doc) { + if (LOG.isDebugEnabled()) { + LOG.debug("onAfterConvert({})", doc); + } + } + + public void onBeforeDelete(CouchbaseDocument doc) { + if (LOG.isDebugEnabled()) { + LOG.debug("onAfterConvert({})", doc); + } + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterDeleteEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterDeleteEvent.java new file mode 100644 index 00000000..607ef88a --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterDeleteEvent.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +/** + * @author Michael Nitschinger + */ +public class AfterDeleteEvent extends CouchbaseMappingEvent { + + public AfterDeleteEvent(E source) { + super(source, null); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveEvent.java new file mode 100644 index 00000000..517ec20d --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveEvent.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; + +/** + * @author Michael Nitschinger + */ +public class AfterSaveEvent extends CouchbaseMappingEvent { + + public AfterSaveEvent(E source, CouchbaseDocument document) { + super(source, document); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertEvent.java new file mode 100644 index 00000000..8a6fec1e --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertEvent.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +/** + * @author Michael Nitschinger + */ +public class BeforeConvertEvent extends CouchbaseMappingEvent { + + public BeforeConvertEvent(E source) { + super(source, null); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeDeleteEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeDeleteEvent.java new file mode 100644 index 00000000..9cc83ba8 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeDeleteEvent.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +/** + * @author Michael Nitschinger + */ +public class BeforeDeleteEvent extends CouchbaseMappingEvent { + + public BeforeDeleteEvent(E source) { + super(source, null); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveEvent.java new file mode 100644 index 00000000..397cc7ca --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveEvent.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; + +/** + * @author Michael Nitschinger + */ +public class BeforeSaveEvent extends CouchbaseMappingEvent { + + public BeforeSaveEvent(E source, CouchbaseDocument document) { + super(source, document); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/CouchbaseMappingEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/CouchbaseMappingEvent.java new file mode 100644 index 00000000..de5da3d4 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/CouchbaseMappingEvent.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +import org.springframework.context.ApplicationEvent; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; + +/** + * A mapping event. + * + * @author Michael Nitschinger + */ +public class CouchbaseMappingEvent extends ApplicationEvent { + + private final CouchbaseDocument document; + + public CouchbaseMappingEvent(T source, CouchbaseDocument document) { + super(source); + this.document = document; + } + + public CouchbaseDocument getDocument() { + return document; + } + + @SuppressWarnings("unchecked") + @Override + public T getSource() { + return (T) super.getSource(); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/LoggingEventListener.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/LoggingEventListener.java new file mode 100644 index 00000000..f78f95db --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/LoggingEventListener.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; + +/** + * A{@link ApplicationListener} for Couchbase mapping events logging the events. + * + * @author Michael Nitschinger + */ +public class LoggingEventListener extends AbstractCouchbaseEventListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(LoggingEventListener.class); + + @Override + public void onBeforeDelete(CouchbaseDocument doc) { + LOGGER.info("onBeforeDelete: {}", doc); + } + + @Override + public void onAfterDelete(CouchbaseDocument doc) { + LOGGER.info("onAfterDelete: {}", doc); + } + + @Override + public void onAfterSave(Object source, CouchbaseDocument doc) { + LOGGER.info("onAfterSave: {}, {}", source, doc); + } + + @Override + public void onBeforeSave(Object source, CouchbaseDocument doc) { + LOGGER.info("onBeforeSave: {}, {}", source, doc); + } + + @Override + public void onBeforeConvert(Object source) { + LOGGER.info("onBeforeConvert: {}, {}", source); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java new file mode 100644 index 00000000..353bebd2 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.util.Assert; + +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 + * @author Michael Nitschinger + */ +public class ValidatingCouchbaseEventListener extends AbstractCouchbaseEventListener { + + private static final Logger LOG = LoggerFactory.getLogger(ValidatingCouchbaseEventListener.class); + + private final Validator validator; + + /** + * Creates a new {@link ValidatingCouchbaseEventListener} using the given {@link Validator}. + * + * @param validator must not be {@literal null}. + */ + public ValidatingCouchbaseEventListener(Validator validator) { + Assert.notNull(validator); + this.validator = validator; + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void onBeforeSave(Object source, CouchbaseDocument dbo) { + + LOG.debug("Validating object: {}", source); + Set violations = validator.validate(source); + + if (!violations.isEmpty()) { + + LOG.info("During object: {} validation violations found: {}", source, violations); + throw new ConstraintViolationException(violations); + } + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListenerTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListenerTests.java new file mode 100644 index 00000000..5de20613 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListenerTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; + +/** + * @author Michael Nitschinger + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = EventContextConfiguration.class) +public class AbstractCouchbaseEventListenerTests { + + @Autowired + private CouchbaseTemplate couchbaseTemplate; + + @Autowired + private SimpleMappingEventListener eventListener; + + @Test + public void shouldEmitEvents() { + assertEquals(0, eventListener.onBeforeSaveEvents.size()); + assertEquals(0, eventListener.onAfterSaveEvents.size()); + assertEquals(0, eventListener.onBeforeConvertEvents.size()); + + couchbaseTemplate.save(new User("john smith", 18)); + + assertEquals(1, eventListener.onBeforeSaveEvents.size()); + assertEquals(1, eventListener.onAfterSaveEvents.size()); + assertEquals(1, eventListener.onBeforeConvertEvents.size()); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/event/EventContextConfiguration.java b/src/test/java/org/springframework/data/couchbase/core/mapping/event/EventContextConfiguration.java new file mode 100644 index 00000000..6015046b --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/event/EventContextConfiguration.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.couchbase.TestApplicationConfig; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +/** + * @author Michael Nitschinger + */ +@Configuration +public class EventContextConfiguration extends TestApplicationConfig { + + @Bean + public LocalValidatorFactoryBean validator() { + return new LocalValidatorFactoryBean(); + } + + @Bean + public ValidatingCouchbaseEventListener validatingCouchbaseEventListener() { + return new ValidatingCouchbaseEventListener(validator()); + } + + @Bean + public SimpleMappingEventListener simpleMappingEventListener() { + return new SimpleMappingEventListener(); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/event/SimpleMappingEventListener.java b/src/test/java/org/springframework/data/couchbase/core/mapping/event/SimpleMappingEventListener.java new file mode 100644 index 00000000..9048f3ca --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/event/SimpleMappingEventListener.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; + +import java.util.ArrayList; + +/** + * @author Michael Nitschinger + */ +public class SimpleMappingEventListener extends AbstractCouchbaseEventListener { + + public final ArrayList> onBeforeConvertEvents = new ArrayList>(); + public final ArrayList> onBeforeSaveEvents = new ArrayList>(); + public final ArrayList> onAfterSaveEvents = new ArrayList>(); + + @Override + public void onBeforeConvert(Object source) { + onBeforeConvertEvents.add(new BeforeConvertEvent(source)); + } + + @Override + public void onBeforeSave(Object source, CouchbaseDocument doc) { + onBeforeSaveEvents.add(new BeforeSaveEvent(source, doc)); + } + + @Override + public void onAfterSave(Object source, CouchbaseDocument doc) { + onAfterSaveEvents.add(new AfterSaveEvent(source, doc)); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/event/User.java b/src/test/java/org/springframework/data/couchbase/core/mapping/event/User.java new file mode 100644 index 00000000..597c7fa5 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/event/User.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +import org.springframework.data.annotation.Id; + +import javax.validation.constraints.Min; +import javax.validation.constraints.Size; + +/** + * @author Michael Nitschinger + */ +public class User { + + @Id + private String id; + + @Size(min = 10) + private String name; + + @Min(18) + private Integer age; + + public User(String name, Integer age) { + id = "id"; + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public Integer getAge() { + return age; + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListenerTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListenerTests.java new file mode 100644 index 00000000..05df2eea --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListenerTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013 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.couchbase.core.mapping.event; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.validation.ConstraintViolationException; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author Michael Nitschinger + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = EventContextConfiguration.class) +public class ValidatingCouchbaseEventListenerTests { + + @Autowired + CouchbaseTemplate template; + + @Test + public void shouldThrowConstraintViolationException() { + User user = new User("john", 17); + + try { + template.save(user); + fail(); + } catch (ConstraintViolationException e) { + assertThat(e.getConstraintViolations().size(), equalTo(2)); + } + } + + @Test + public void shouldNotThrowAnyExceptions() { + template.save(new User("john smith", 18)); + } +} diff --git a/template.mf b/template.mf index cf01b4ea..3e396567 100644 --- a/template.mf +++ b/template.mf @@ -15,4 +15,5 @@ Import-Template: org.w3c.*;version="0.0.0", org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, org.slf4j.*;version="${slf4j:[=.=.=,+1.0.0)}", - org.joda.time.*;version="${jodatime:[=.=.=,+1.0.0)}";resolution:=optional + org.joda.time.*;version="${jodatime:[=.=.=,+1.0.0)}";resolution:=optional, + javax.validation.*;version="${validation:[=.=.=.=,+1.0.0)}";resolution:=optional