From 7f990b006b983962761fc0bf9abcb373c8891dbe Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 4 Dec 2014 18:56:29 +0100 Subject: [PATCH] DATAREST-388 - Improved annotation based event handling. The annotation based event handling now relies on the type of the first method argument to determine the domain type the handler is interested in. Improved method invocation to not unnecessarily wrap exceptions thrown from them. Changed the test cases to throw a dedicated runtime exception to implicitly test that the Renamed LinkSaveEvent to LinkedEntityEvent as it's not only used for save-events for entities. Make use of Methods' USER_METHOD filter. Moved the class into the util package. Removed the UUID converter as Spring's DefaultFormattingConversionService provides it out of the box. Added missing license headers and JavaDoc. Deprecated Class attributes on handling annotations. Related pull request: #151. --- .../core/annotation/HandleAfterCreate.java | 25 ++- .../core/annotation/HandleAfterDelete.java | 25 ++- .../annotation/HandleAfterLinkDelete.java | 25 ++- .../core/annotation/HandleAfterLinkSave.java | 25 ++- .../rest/core/annotation/HandleAfterSave.java | 25 ++- .../core/annotation/HandleBeforeCreate.java | 25 ++- .../core/annotation/HandleBeforeDelete.java | 25 ++- .../annotation/HandleBeforeLinkDelete.java | 25 ++- .../core/annotation/HandleBeforeLinkSave.java | 25 ++- .../core/annotation/HandleBeforeSave.java | 25 ++- .../annotation/RepositoryEventHandler.java | 23 +- .../rest/core/event/AfterLinkDeleteEvent.java | 2 +- .../rest/core/event/AfterLinkSaveEvent.java | 2 +- .../event/AnnotatedEventHandlerInvoker.java | 198 ++++++++++++++++++ .../AnnotatedHandlerBeanPostProcessor.java | 170 --------------- .../core/event/BeforeLinkDeleteEvent.java | 2 +- .../rest/core/event/BeforeLinkSaveEvent.java | 2 +- .../data/rest/core/event/LinkSaveEvent.java | 27 --- .../rest/core/event/LinkedEntityEvent.java | 49 +++++ .../rest/core/{support => util}/Methods.java | 11 +- .../data/rest/core/util/UUIDConverter.java | 74 ------- .../RepositoryEventIntegrationTests.java | 74 +++++-- .../jpa/AnnotatedPersonEventHandler.java | 29 ++- .../jpa/EventHandlerInvokedException.java | 24 +++ .../domain/jpa/PersonBeforeSaveHandler.java | 23 +- .../RepositoryRestMvcConfiguration.java | 8 +- src/main/asciidoc/events.adoc | 58 ++--- 27 files changed, 675 insertions(+), 351 deletions(-) create mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AnnotatedEventHandlerInvoker.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AnnotatedHandlerBeanPostProcessor.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/LinkSaveEvent.java create mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/LinkedEntityEvent.java rename spring-data-rest-core/src/main/java/org/springframework/data/rest/core/{support => util}/Methods.java (65%) delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UUIDConverter.java create mode 100644 spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/EventHandlerInvokedException.java diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterCreate.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterCreate.java index 18d33d473..4cbcc0dcc 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterCreate.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterCreate.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.annotation; import java.lang.annotation.ElementType; @@ -8,12 +23,20 @@ import java.lang.annotation.Target; /** * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HandleAfterCreate { + /** + * The domain type which you want to listen for events for. + * + * @deprecated the domain type of interest is derived from the type of the first parameter of the annotated handler + * method. + * @return + */ + @Deprecated Class[] value() default {}; - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterDelete.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterDelete.java index 1731bb5f3..8d6520a1b 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterDelete.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterDelete.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.annotation; import java.lang.annotation.ElementType; @@ -10,12 +25,20 @@ import java.lang.annotation.Target; * Denotes a component that should handle the {@literal afterDelete} event. * * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HandleAfterDelete { + /** + * The domain type which you want to listen for events for. + * + * @deprecated the domain type of interest is derived from the type of the first parameter of the annotated handler + * method. + * @return + */ + @Deprecated Class[] value() default {}; - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterLinkDelete.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterLinkDelete.java index e406a420a..36b5f56c7 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterLinkDelete.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterLinkDelete.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.annotation; import java.lang.annotation.ElementType; @@ -10,12 +25,20 @@ import java.lang.annotation.Target; * Denotes a component that should handle the {@literal afterLinkDelete} event. * * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HandleAfterLinkDelete { + /** + * The domain type which you want to listen for events for. + * + * @deprecated the domain type of interest is derived from the type of the first parameter of the annotated handler + * method. + * @return + */ + @Deprecated Class[] value() default {}; - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterLinkSave.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterLinkSave.java index 2c5019b77..9474fc288 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterLinkSave.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterLinkSave.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.annotation; import java.lang.annotation.ElementType; @@ -10,12 +25,20 @@ import java.lang.annotation.Target; * Denotes a component that should handle the {@literal afterLinkSave} event. * * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HandleAfterLinkSave { + /** + * The domain type which you want to listen for events for. + * + * @deprecated the domain type of interest is derived from the type of the first parameter of the annotated handler + * method. + * @return + */ + @Deprecated Class[] value() default {}; - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterSave.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterSave.java index 3e400bda2..a7135c7c1 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterSave.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleAfterSave.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.annotation; import java.lang.annotation.ElementType; @@ -10,12 +25,20 @@ import java.lang.annotation.Target; * Denotes a component that should handle the {@literal afterSave} event. * * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HandleAfterSave { + /** + * The domain type which you want to listen for events for. + * + * @deprecated the domain type of interest is derived from the type of the first parameter of the annotated handler + * method. + * @return + */ + @Deprecated Class[] value() default {}; - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeCreate.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeCreate.java index 1a89bae1d..3aeaf6bf2 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeCreate.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeCreate.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.annotation; import java.lang.annotation.ElementType; @@ -8,12 +23,20 @@ import java.lang.annotation.Target; /** * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HandleBeforeCreate { + /** + * The domain type which you want to listen for events for. + * + * @deprecated the domain type of interest is derived from the type of the first parameter of the annotated handler + * method. + * @return + */ + @Deprecated Class[] value() default {}; - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeDelete.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeDelete.java index 69aa9a79d..27d3214ee 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeDelete.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeDelete.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.annotation; import java.lang.annotation.ElementType; @@ -10,12 +25,20 @@ import java.lang.annotation.Target; * Denotes a component that should handle the {@literal beforeDelete} event. * * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HandleBeforeDelete { + /** + * The domain type which you want to listen for events for. + * + * @deprecated the domain type of interest is derived from the type of the first parameter of the annotated handler + * method. + * @return + */ + @Deprecated Class[] value() default {}; - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeLinkDelete.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeLinkDelete.java index d32d49820..6b4a99501 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeLinkDelete.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeLinkDelete.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.annotation; import java.lang.annotation.ElementType; @@ -10,12 +25,20 @@ import java.lang.annotation.Target; * Denotes a component that should handle the {@literal beforeLinkDelete} event. * * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HandleBeforeLinkDelete { + /** + * The domain type which you want to listen for events for. + * + * @deprecated the domain type of interest is derived from the type of the first parameter of the annotated handler + * method. + * @return + */ + @Deprecated Class[] value() default {}; - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeLinkSave.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeLinkSave.java index c5f4893bf..74c681779 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeLinkSave.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeLinkSave.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.annotation; import java.lang.annotation.ElementType; @@ -10,12 +25,20 @@ import java.lang.annotation.Target; * Denotes a component that should handle the {@literal beforeLinkSave} event. * * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HandleBeforeLinkSave { + /** + * The domain type which you want to listen for events for. + * + * @deprecated the domain type of interest is derived from the type of the first parameter of the annotated handler + * method. + * @return + */ + @Deprecated Class[] value() default {}; - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeSave.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeSave.java index c3c4e02be..824c4d9fc 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeSave.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/HandleBeforeSave.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.annotation; import java.lang.annotation.ElementType; @@ -10,12 +25,20 @@ import java.lang.annotation.Target; * Denotes a component that should handle the {@literal beforeSave} event. * * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HandleBeforeSave { + /** + * The domain type which you want to listen for events for. + * + * @deprecated the domain type of interest is derived from the type of the first parameter of the annotated handler + * method. + * @return + */ + @Deprecated Class[] value() default {}; - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/RepositoryEventHandler.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/RepositoryEventHandler.java index 22f5a03e4..8ee76eb4b 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/RepositoryEventHandler.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/annotation/RepositoryEventHandler.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.annotation; import java.lang.annotation.ElementType; @@ -9,7 +24,8 @@ import java.lang.annotation.Target; /** * Advertises classes annotated with this that they are event handlers. * - * @author Jon Brisbin + * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @@ -18,7 +34,10 @@ public @interface RepositoryEventHandler { /** * The list of {@link org.springframework.context.ApplicationEvent} classes this event handler cares about. + * + * @deprecated the type the handler is interested in is determined by the type of the first parameter of a handler + * method. */ + @Deprecated Class[] value() default {}; - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AfterLinkDeleteEvent.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AfterLinkDeleteEvent.java index f06d2aa99..764354bdd 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AfterLinkDeleteEvent.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AfterLinkDeleteEvent.java @@ -5,7 +5,7 @@ package org.springframework.data.rest.core.event; * * @author Jon Brisbin */ -public class AfterLinkDeleteEvent extends LinkSaveEvent { +public class AfterLinkDeleteEvent extends LinkedEntityEvent { private static final long serialVersionUID = 3887575011761146290L; diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AfterLinkSaveEvent.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AfterLinkSaveEvent.java index 7997437f8..ae8e62370 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AfterLinkSaveEvent.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AfterLinkSaveEvent.java @@ -5,7 +5,7 @@ package org.springframework.data.rest.core.event; * * @author Jon Brisbin */ -public class AfterLinkSaveEvent extends LinkSaveEvent { +public class AfterLinkSaveEvent extends LinkedEntityEvent { private static final long serialVersionUID = 261522353893713633L; diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AnnotatedEventHandlerInvoker.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AnnotatedEventHandlerInvoker.java new file mode 100644 index 000000000..9ae69a2b2 --- /dev/null +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AnnotatedEventHandlerInvoker.java @@ -0,0 +1,198 @@ +/* + * Copyright 2012-2014 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.rest.core.event; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.rest.core.annotation.HandleAfterCreate; +import org.springframework.data.rest.core.annotation.HandleAfterDelete; +import org.springframework.data.rest.core.annotation.HandleAfterLinkDelete; +import org.springframework.data.rest.core.annotation.HandleAfterLinkSave; +import org.springframework.data.rest.core.annotation.HandleAfterSave; +import org.springframework.data.rest.core.annotation.HandleBeforeCreate; +import org.springframework.data.rest.core.annotation.HandleBeforeDelete; +import org.springframework.data.rest.core.annotation.HandleBeforeLinkDelete; +import org.springframework.data.rest.core.annotation.HandleBeforeLinkSave; +import org.springframework.data.rest.core.annotation.HandleBeforeSave; +import org.springframework.data.rest.core.annotation.RepositoryEventHandler; +import org.springframework.data.rest.core.util.Methods; +import org.springframework.util.ClassUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.ReflectionUtils; + +/** + * Component to discover annotated repository event handlers and trigger them on {@link ApplicationEvent}s. + * + * @author Jon Brisbin + * @author Oliver Gierke + */ +public class AnnotatedEventHandlerInvoker implements ApplicationListener, BeanPostProcessor { + + private static final Logger LOG = LoggerFactory.getLogger(AnnotatedEventHandlerInvoker.class); + private static final String PARAMETER_MISSING = "Invalid event handler method %s! At least a single argument is required to determine the domain type for which you are interested in events."; + + private final MultiValueMap, EventHandlerMethod> handlerMethods = new LinkedMultiValueMap, EventHandlerMethod>(); + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) + */ + @Override + public void onApplicationEvent(RepositoryEvent event) { + + Class eventType = event.getClass(); + + if (!handlerMethods.containsKey(eventType)) { + return; + } + + for (EventHandlerMethod handlerMethod : handlerMethods.get(eventType)) { + + Object src = event.getSource(); + + if (!ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) { + continue; + } + + List parameters = new ArrayList(); + parameters.add(src); + + if (event instanceof LinkedEntityEvent) { + parameters.add(((LinkedEntityEvent) event).getLinked()); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Invoking {} handler for {}.", event.getClass().getSimpleName(), event.getSource()); + } + + ReflectionUtils.invokeMethod(handlerMethod.method, handlerMethod.handler, parameters.toArray()); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String) + */ + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String) + */ + @Override + public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException { + final Class beanType = bean.getClass(); + + RepositoryEventHandler typeAnno = AnnotationUtils.findAnnotation(beanType, RepositoryEventHandler.class); + + if (typeAnno == null) { + return bean; + } + + ReflectionUtils.doWithMethods(beanType, new ReflectionUtils.MethodCallback() { + + /* + * (non-Javadoc) + * @see org.springframework.util.ReflectionUtils.MethodCallback#doWith(java.lang.reflect.Method) + */ + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + + inspect(bean, method, HandleBeforeCreate.class, BeforeCreateEvent.class); + inspect(bean, method, HandleAfterCreate.class, AfterCreateEvent.class); + inspect(bean, method, HandleBeforeSave.class, BeforeSaveEvent.class); + inspect(bean, method, HandleAfterSave.class, AfterSaveEvent.class); + inspect(bean, method, HandleBeforeLinkSave.class, BeforeLinkSaveEvent.class); + inspect(bean, method, HandleAfterLinkSave.class, AfterLinkSaveEvent.class); + inspect(bean, method, HandleBeforeDelete.class, BeforeDeleteEvent.class); + inspect(bean, method, HandleAfterDelete.class, AfterDeleteEvent.class); + inspect(bean, method, HandleBeforeLinkDelete.class, BeforeLinkDeleteEvent.class); + inspect(bean, method, HandleAfterLinkDelete.class, AfterLinkDeleteEvent.class); + } + }, Methods.USER_METHODS); + + return bean; + } + + /** + * Inspects the given handler method for an annotation of the given type. If the annotation present an + * {@link EventHandlerMethod} is registered for the given {@link RepositoryEvent} type. + * + * @param handler must not be {@literal null}. + * @param method must not be {@literal null}. + * @param annotationType must not be {@literal null}. + * @param eventType must not be {@literal null}. + */ + private void inspect(Object handler, Method method, Class annotationType, + Class eventType) { + + T annotation = AnnotationUtils.findAnnotation(method, annotationType); + + if (annotation == null) { + return; + } + + Class[] parameterTypes = method.getParameterTypes(); + + if (parameterTypes.length == 0) { + throw new IllegalStateException(String.format(PARAMETER_MISSING, method)); + } + + EventHandlerMethod handlerMethod = new EventHandlerMethod(parameterTypes[0], handler, method); + + if (LOG.isDebugEnabled()) { + LOG.debug("Annotated handler method found: {}", handlerMethod); + } + + handlerMethods.add(eventType, handlerMethod); + } + + private static class EventHandlerMethod { + + final Class targetType; + final Method method; + final Object handler; + + private EventHandlerMethod(Class targetType, Object handler, Method method) { + this.targetType = targetType; + this.method = method; + this.handler = handler; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("EventHandlerMethod{ targetType=%s, method=%s, handler=%s }", targetType, method, handler); + } + } +} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AnnotatedHandlerBeanPostProcessor.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AnnotatedHandlerBeanPostProcessor.java deleted file mode 100644 index 62e434cb6..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/AnnotatedHandlerBeanPostProcessor.java +++ /dev/null @@ -1,170 +0,0 @@ -package org.springframework.data.rest.core.event; - -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.ApplicationListener; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.data.rest.core.annotation.HandleAfterCreate; -import org.springframework.data.rest.core.annotation.HandleAfterDelete; -import org.springframework.data.rest.core.annotation.HandleAfterLinkDelete; -import org.springframework.data.rest.core.annotation.HandleAfterLinkSave; -import org.springframework.data.rest.core.annotation.HandleAfterSave; -import org.springframework.data.rest.core.annotation.HandleBeforeCreate; -import org.springframework.data.rest.core.annotation.HandleBeforeDelete; -import org.springframework.data.rest.core.annotation.HandleBeforeLinkDelete; -import org.springframework.data.rest.core.annotation.HandleBeforeLinkSave; -import org.springframework.data.rest.core.annotation.HandleBeforeSave; -import org.springframework.data.rest.core.annotation.RepositoryEventHandler; -import org.springframework.util.ClassUtils; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.ReflectionUtils; - -/** - * @author Jon Brisbin - */ -public class AnnotatedHandlerBeanPostProcessor implements ApplicationListener, BeanPostProcessor { - - private static final Logger LOG = LoggerFactory.getLogger(AnnotatedHandlerBeanPostProcessor.class); - private final MultiValueMap, EventHandlerMethod> handlerMethods = new LinkedMultiValueMap, AnnotatedHandlerBeanPostProcessor.EventHandlerMethod>(); - - @Override - public void onApplicationEvent(RepositoryEvent event) { - Class eventType = event.getClass(); - if (!handlerMethods.containsKey(eventType)) { - return; - } - - for (EventHandlerMethod handlerMethod : handlerMethods.get(eventType)) { - try { - Object src = event.getSource(); - - if (!ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) { - continue; - } - - List params = new ArrayList(); - params.add(src); - if (event instanceof BeforeLinkSaveEvent) { - params.add(((BeforeLinkSaveEvent) event).getLinked()); - } else if (event instanceof AfterLinkSaveEvent) { - params.add(((AfterLinkSaveEvent) event).getLinked()); - } - - if (LOG.isDebugEnabled()) { - LOG.debug("Invoking " + event.getClass().getSimpleName() + " handler for " + event.getSource()); - } - handlerMethod.method.invoke(handlerMethod.handler, params.toArray()); - - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - return bean; - } - - @Override - public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException { - final Class beanType = bean.getClass(); - - RepositoryEventHandler typeAnno = AnnotationUtils.findAnnotation(beanType, RepositoryEventHandler.class); - if (null == typeAnno) { - return bean; - } - - Class[] targetTypes = typeAnno.value(); - if (targetTypes.length == 0) { - targetTypes = new Class[] { null }; - } - - for (final Class targetType : targetTypes) { - ReflectionUtils.doWithMethods(beanType, new ReflectionUtils.MethodCallback() { - @Override - public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - inspect(targetType, bean, method, HandleBeforeCreate.class, BeforeCreateEvent.class); - inspect(targetType, bean, method, HandleAfterCreate.class, AfterCreateEvent.class); - inspect(targetType, bean, method, HandleBeforeSave.class, BeforeSaveEvent.class); - inspect(targetType, bean, method, HandleAfterSave.class, AfterSaveEvent.class); - inspect(targetType, bean, method, HandleBeforeLinkSave.class, BeforeLinkSaveEvent.class); - inspect(targetType, bean, method, HandleAfterLinkSave.class, AfterLinkSaveEvent.class); - inspect(targetType, bean, method, HandleBeforeDelete.class, BeforeDeleteEvent.class); - inspect(targetType, bean, method, HandleAfterDelete.class, AfterDeleteEvent.class); - inspect(targetType, bean, method, HandleBeforeLinkDelete.class, BeforeLinkDeleteEvent.class); - inspect(targetType, bean, method, HandleAfterLinkDelete.class, AfterLinkDeleteEvent.class); - } - }, new ReflectionUtils.MethodFilter() { - @Override - public boolean matches(Method method) { - return (!method.isSynthetic() && !method.isBridge() && method.getDeclaringClass() != Object.class && !method - .getName().contains("$")); - } - }); - } - - return bean; - } - - private void inspect(Class targetType, Object handler, Method method, Class annoType, - Class eventType) { - T anno = method.getAnnotation(annoType); - if (null != anno) { - try { - Class[] targetTypes; - if (null == targetType) { - targetTypes = (Class[]) anno.getClass().getMethod("value", new Class[0]).invoke(anno); - } else { - targetTypes = new Class[] { targetType }; - } - for (Class type : targetTypes) { - EventHandlerMethod m = new EventHandlerMethod(type, handler, method); - if (LOG.isDebugEnabled()) { - LOG.debug("Annotated handler method found: " + m); - } - handlerMethods.add(eventType, m); - } - } catch (NoSuchMethodException e) { - if (LOG.isDebugEnabled()) { - LOG.debug(e.getMessage(), e); - } - } catch (InvocationTargetException e) { - if (LOG.isDebugEnabled()) { - LOG.debug(e.getMessage(), e); - } - } catch (IllegalAccessException e) { - if (LOG.isDebugEnabled()) { - LOG.debug(e.getMessage(), e); - } - } - } - } - - private class EventHandlerMethod { - final Class targetType; - final Method method; - final Object handler; - - private EventHandlerMethod(Class targetType, Object handler, Method method) { - this.targetType = targetType; - this.method = method; - this.handler = handler; - } - - @Override - public String toString() { - return "EventHandlerMethod{" + "targetType=" + targetType + ", method=" + method + ", handler=" + handler + '}'; - } - } - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/BeforeLinkDeleteEvent.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/BeforeLinkDeleteEvent.java index 49f22a01a..eb058ed80 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/BeforeLinkDeleteEvent.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/BeforeLinkDeleteEvent.java @@ -5,7 +5,7 @@ package org.springframework.data.rest.core.event; * * @author Jon Brisbin */ -public class BeforeLinkDeleteEvent extends LinkSaveEvent { +public class BeforeLinkDeleteEvent extends LinkedEntityEvent { private static final long serialVersionUID = -973540913790564962L; diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/BeforeLinkSaveEvent.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/BeforeLinkSaveEvent.java index baba520de..8dc560c3c 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/BeforeLinkSaveEvent.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/BeforeLinkSaveEvent.java @@ -5,7 +5,7 @@ package org.springframework.data.rest.core.event; * * @author Jon Brisbin */ -public class BeforeLinkSaveEvent extends LinkSaveEvent { +public class BeforeLinkSaveEvent extends LinkedEntityEvent { private static final long serialVersionUID = 4836932640633578985L; diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/LinkSaveEvent.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/LinkSaveEvent.java deleted file mode 100644 index f33c525ab..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/LinkSaveEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.springframework.data.rest.core.event; - -/** - * Base class for {@link RepositoryEvent}s that deal with saving/updating or deleting a linked object. - * - * @author Jon Brisbin - */ -public abstract class LinkSaveEvent extends RepositoryEvent { - - private static final long serialVersionUID = -9071648572128698903L; - private final Object linked; - - public LinkSaveEvent(Object source, Object linked) { - super(source); - this.linked = linked; - } - - /** - * Get the linked object. - * - * @return The entity representing the right-hand side of this relationship. - */ - public Object getLinked() { - return linked; - } - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/LinkedEntityEvent.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/LinkedEntityEvent.java new file mode 100644 index 000000000..1c3079ef9 --- /dev/null +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/LinkedEntityEvent.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2014 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.rest.core.event; + +/** + * Base class for {@link RepositoryEvent}s that deal with saving/updating or deleting a linked object. + * + * @author Jon Brisbin + * @author Oliver Gierke + */ +public abstract class LinkedEntityEvent extends RepositoryEvent { + + private static final long serialVersionUID = -9071648572128698903L; + private final Object linked; + + /** + * Creates a new {@link LinkedEntityEvent} for th given source and linked instance. + * + * @param source must not be {@literal null}. + * @param linked can be {@literal null}. + */ + public LinkedEntityEvent(Object source, Object linked) { + + super(source); + this.linked = linked; + } + + /** + * Get the linked object. + * + * @return The entity representing the right-hand side of this relationship. + */ + public Object getLinked() { + return linked; + } +} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/support/Methods.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/Methods.java similarity index 65% rename from spring-data-rest-core/src/main/java/org/springframework/data/rest/core/support/Methods.java rename to spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/Methods.java index dbf1ad3f4..a1774208c 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/support/Methods.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/Methods.java @@ -1,24 +1,27 @@ -package org.springframework.data.rest.core.support; +package org.springframework.data.rest.core.util; import java.lang.reflect.Method; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.util.ReflectionUtils; /** * @author Jon Brisbin + * @author Oliver Gierke */ public abstract class Methods { private Methods() {} public static final ReflectionUtils.MethodFilter USER_METHODS = new ReflectionUtils.MethodFilter() { + + /* + * (non-Javadoc) + * @see org.springframework.util.ReflectionUtils.MethodFilter#matches(java.lang.reflect.Method) + */ @Override public boolean matches(Method method) { return (!method.isSynthetic() && !method.isBridge() && method.getDeclaringClass() != Object.class && !method .getName().contains("$")); } }; - public static final LocalVariableTableParameterNameDiscoverer NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer(); - } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UUIDConverter.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UUIDConverter.java deleted file mode 100644 index 4aa4e9716..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UUIDConverter.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2012-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.rest.core.util; - -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.converter.ConditionalGenericConverter; - -/** - * For converting a {@link UUID} into a {@link String}. - * - * @author Jon Brisbin - */ -public class UUIDConverter implements ConditionalGenericConverter { - - public static final UUIDConverter INSTANCE = new UUIDConverter(); - private static final Set CONVERTIBLE_PAIRS = new HashSet(); - - static { - CONVERTIBLE_PAIRS.add(new ConvertiblePair(String.class, UUID.class)); - CONVERTIBLE_PAIRS.add(new ConvertiblePair(UUID.class, String.class)); - } - - /* - * (non-Javadoc) - * @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) - */ - @Override - public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - if (String.class.isAssignableFrom(sourceType.getType())) { - return UUID.class.isAssignableFrom(targetType.getType()); - } - - return UUID.class.isAssignableFrom(sourceType.getType()) && String.class.isAssignableFrom(targetType.getType()); - } - - /* - * (non-Javadoc) - * @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes() - */ - @Override - public Set getConvertibleTypes() { - return CONVERTIBLE_PAIRS; - } - - /* - * (non-Javadoc) - * @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) - */ - @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - if (String.class.isAssignableFrom(sourceType.getType())) { - return UUID.fromString(source.toString()); - } else { - return source.toString(); - } - } -} diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/context/RepositoryEventIntegrationTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/context/RepositoryEventIntegrationTests.java index e1ac7aedc..55ecb412a 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/context/RepositoryEventIntegrationTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/context/RepositoryEventIntegrationTests.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 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.rest.core.context; import org.junit.Before; @@ -10,6 +25,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.rest.core.RepositoryTestsConfig; import org.springframework.data.rest.core.domain.jpa.AnnotatedPersonEventHandler; +import org.springframework.data.rest.core.domain.jpa.EventHandlerInvokedException; import org.springframework.data.rest.core.domain.jpa.Person; import org.springframework.data.rest.core.domain.jpa.PersonBeforeSaveHandler; import org.springframework.data.rest.core.domain.jpa.PersonRepository; @@ -18,7 +34,7 @@ import org.springframework.data.rest.core.event.AfterDeleteEvent; import org.springframework.data.rest.core.event.AfterLinkDeleteEvent; import org.springframework.data.rest.core.event.AfterLinkSaveEvent; import org.springframework.data.rest.core.event.AfterSaveEvent; -import org.springframework.data.rest.core.event.AnnotatedHandlerBeanPostProcessor; +import org.springframework.data.rest.core.event.AnnotatedEventHandlerInvoker; import org.springframework.data.rest.core.event.BeforeCreateEvent; import org.springframework.data.rest.core.event.BeforeDeleteEvent; import org.springframework.data.rest.core.event.BeforeLinkDeleteEvent; @@ -32,6 +48,7 @@ import org.springframework.transaction.annotation.Transactional; * Tests around the {@link org.springframework.context.ApplicationEvent} handling abstractions. * * @author Jon Brisbin + * @author Oliver Gierke */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @@ -53,8 +70,8 @@ public class RepositoryEventIntegrationTests { } @Bean - public AnnotatedHandlerBeanPostProcessor annotatedHandlerBeanPostProcessor() { - return new AnnotatedHandlerBeanPostProcessor(); + public static AnnotatedEventHandlerInvoker annotatedEventHandlerInvoker() { + return new AnnotatedEventHandlerInvoker(); } } @@ -67,54 +84,83 @@ public class RepositoryEventIntegrationTests { person = people.save(new Person("Jane", "Doe")); } - @Test(expected = RuntimeException.class) + /** + * @see DATAREST-388 + */ + @Test(expected = EventHandlerInvokedException.class) public void shouldDispatchBeforeCreate() throws Exception { appCtx.publishEvent(new BeforeCreateEvent(person)); } - @Test(expected = RuntimeException.class) + /** + * @see DATAREST-388 + */ + @Test(expected = EventHandlerInvokedException.class) public void shouldDispatchAfterCreate() throws Exception { appCtx.publishEvent(new AfterCreateEvent(person)); } - @Test(expected = RuntimeException.class) + /** + * @see DATAREST-388 + */ + @Test(expected = EventHandlerInvokedException.class) public void shouldDispatchBeforeSave() throws Exception { appCtx.publishEvent(new BeforeSaveEvent(person)); } - @Test(expected = RuntimeException.class) + /** + * @see DATAREST-388 + */ + @Test(expected = EventHandlerInvokedException.class) public void shouldDispatchAfterSave() throws Exception { appCtx.publishEvent(new AfterSaveEvent(person)); } - @Test(expected = RuntimeException.class) + /** + * @see DATAREST-388 + */ + @Test(expected = EventHandlerInvokedException.class) public void shouldDispatchBeforeDelete() throws Exception { appCtx.publishEvent(new BeforeDeleteEvent(person)); } - @Test(expected = RuntimeException.class) + /** + * @see DATAREST-388 + */ + @Test(expected = EventHandlerInvokedException.class) public void shouldDispatchAfterDelete() throws Exception { appCtx.publishEvent(new AfterDeleteEvent(person)); } - @Test(expected = RuntimeException.class) + /** + * @see DATAREST-388 + */ + @Test(expected = EventHandlerInvokedException.class) public void shouldDispatchBeforeLinkSave() throws Exception { appCtx.publishEvent(new BeforeLinkSaveEvent(person, new Object())); } - @Test(expected = RuntimeException.class) + /** + * @see DATAREST-388 + */ + @Test(expected = EventHandlerInvokedException.class) public void shouldDispatchAfterLinkSave() throws Exception { appCtx.publishEvent(new AfterLinkSaveEvent(person, new Object())); } - @Test(expected = RuntimeException.class) + /** + * @see DATAREST-388 + */ + @Test(expected = EventHandlerInvokedException.class) public void shouldDispatchBeforeLinkDelete() throws Exception { appCtx.publishEvent(new BeforeLinkDeleteEvent(person, new Object())); } - @Test(expected = RuntimeException.class) + /** + * @see DATAREST-388 + */ + @Test(expected = EventHandlerInvokedException.class) public void shouldDispatchAfterLinkDelete() throws Exception { appCtx.publishEvent(new AfterLinkDeleteEvent(person, new Object())); } - } diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/AnnotatedPersonEventHandler.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/AnnotatedPersonEventHandler.java index 1bba59f9f..2f154c880 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/AnnotatedPersonEventHandler.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/AnnotatedPersonEventHandler.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-2014 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.rest.core.domain.jpa; import org.springframework.data.rest.core.annotation.HandleAfterCreate; @@ -13,33 +28,37 @@ import org.springframework.data.rest.core.annotation.HandleBeforeSave; import org.springframework.data.rest.core.annotation.RepositoryEventHandler; /** + * Sample annotation-based event handler. + * * @author Jon Brisbin + * @author Oliver Gierke */ -@RepositoryEventHandler(Person.class) +@RepositoryEventHandler public class AnnotatedPersonEventHandler { + @HandleAfterCreate @HandleAfterDelete @HandleAfterSave public void handleAfter(Person p) { - throw new RuntimeException(); + throw new EventHandlerInvokedException(); } @HandleAfterLinkDelete @HandleAfterLinkSave public void handleAfterLink(Person p, Object o) { - throw new RuntimeException(); + throw new EventHandlerInvokedException(); } @HandleBeforeCreate @HandleBeforeDelete @HandleBeforeSave public void handleBefore(Person p) { - throw new RuntimeException(); + throw new EventHandlerInvokedException(); } @HandleBeforeLinkDelete @HandleBeforeLinkSave public void handleBeforeLink(Person p, Object o) { - throw new RuntimeException(); + throw new EventHandlerInvokedException(); } } diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/EventHandlerInvokedException.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/EventHandlerInvokedException.java new file mode 100644 index 000000000..0d5cb8678 --- /dev/null +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/EventHandlerInvokedException.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 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.rest.core.domain.jpa; + +/** + * @author Oliver Gierke + */ +public class EventHandlerInvokedException extends RuntimeException { + + private static final long serialVersionUID = -7879286986960261090L; +} diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/PersonBeforeSaveHandler.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/PersonBeforeSaveHandler.java index 83d06ce6c..df09424da 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/PersonBeforeSaveHandler.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/jpa/PersonBeforeSaveHandler.java @@ -1,13 +1,34 @@ +/* + * Copyright 2012-2014 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.rest.core.domain.jpa; import org.springframework.data.rest.core.event.AbstractRepositoryEventListener; /** * @author Jon Brisbin + * @author Oliver Gierke */ public class PersonBeforeSaveHandler extends AbstractRepositoryEventListener { + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.event.AbstractRepositoryEventListener#onBeforeSave(java.lang.Object) + */ @Override protected void onBeforeSave(Person person) { - throw new RuntimeException(); + throw new EventHandlerInvokedException(); } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java index e8c090047..b163fa9b7 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java @@ -57,7 +57,7 @@ import org.springframework.data.rest.core.config.MetadataConfiguration; import org.springframework.data.rest.core.config.Projection; import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; -import org.springframework.data.rest.core.event.AnnotatedHandlerBeanPostProcessor; +import org.springframework.data.rest.core.event.AnnotatedEventHandlerInvoker; import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener; import org.springframework.data.rest.core.mapping.RepositoryResourceMappings; import org.springframework.data.rest.core.mapping.ResourceDescription; @@ -65,7 +65,6 @@ import org.springframework.data.rest.core.mapping.ResourceMappings; import org.springframework.data.rest.core.projection.ProxyProjectionFactory; import org.springframework.data.rest.core.support.DomainObjectMerger; import org.springframework.data.rest.core.support.RepositoryRelProvider; -import org.springframework.data.rest.core.util.UUIDConverter; import org.springframework.data.rest.webmvc.BaseUri; import org.springframework.data.rest.webmvc.BaseUriAwareController; import org.springframework.data.rest.webmvc.BaseUriAwareHandlerMapping; @@ -181,7 +180,6 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon public DefaultFormattingConversionService defaultConversionService() { DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); - conversionService.addConverter(UUIDConverter.INSTANCE); configureConversionService(conversionService); if (!conversionService.canConvert(String.class, Point.class)) { @@ -259,8 +257,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon * @return */ @Bean - public static AnnotatedHandlerBeanPostProcessor annotatedHandlerBeanPostProcessor() { - return new AnnotatedHandlerBeanPostProcessor(); + public static AnnotatedEventHandlerInvoker annotatedEventHandlerInvoker() { + return new AnnotatedEventHandlerInvoker(); } /** diff --git a/src/main/asciidoc/events.adoc b/src/main/asciidoc/events.adoc index cd2e7100b..751350d11 100644 --- a/src/main/asciidoc/events.adoc +++ b/src/main/asciidoc/events.adoc @@ -1,20 +1,20 @@ [[events-chapter]] = Events -There are eight different events that the REST exporter emits throughout the process of working with an entity. Those are: +There are eight different events that the REST exporter emits throughout the process of working with an entity. Those are: -* BeforeCreateEvent -* AfterCreateEvent -* BeforeSaveEvent -* AfterSaveEvent -* BeforeLinkSaveEvent -* AfterLinkSaveEvent -* BeforeDeleteEvent -* AfterDeleteEvent +* `BeforeCreateEvent` +* `AfterCreateEvent` +* `BeforeSaveEvent` +* `AfterSaveEvent` +* `BeforeLinkSaveEvent` +* `AfterLinkSaveEvent` +* `BeforeDeleteEvent` +* `AfterDeleteEvent` == Writing an ApplicationListener -There is an abstract class you can subclass which listens for these kinds of events and calls the appropriate method based on the event type. You just override the methods for the events you're interested in. +There is an abstract class you can subclass which listens for these kinds of events and calls the appropriate method based on the event type. You just override the methods for the events you're interested in. [source,java] ---- @@ -30,56 +30,44 @@ public class BeforeSaveEventListener extends AbstractRepositoryEventListener { } ---- -One thing to note with this approach, however, is that it makes no distinction based on the type of the entity. You'll have to inspect that yourself. +One thing to note with this approach, however, is that it makes no distinction based on the type of the entity. You'll have to inspect that yourself. == Writing an annotated handler Another approach is to use an annotated handler, which does filter events based on domain type. -To declare a handler, create a POJO and put the `@RepositoryEventHandler` annotation on it. This tells the `BeanPostProcessor` that this class needs to be inspected for handler methods. +To declare a handler, create a POJO and put the `@RepositoryEventHandler` annotation on it. This tells the `BeanPostProcessor` that this class needs to be inspected for handler methods. -Once it finds a bean with this annotation, it iterates over the exposed methods and looks for annotations that correspond to the event you're interested in. For example, to handle BeforeSaveEvents in an annotated POJO for different kinds of domain types, you'd define your class like this: +Once it finds a bean with this annotation, it iterates over the exposed methods and looks for annotations that correspond to the event you're interested in. For example, to handle `BeforeSaveEvent`s in an annotated POJO for different kinds of domain types, you'd define your class like this: [source,java] ---- @RepositoryEventHandler public class PersonEventHandler { - @HandleBeforeSave(Person.class) public void handlePersonSave(Person p) { - ... you can now deal with Person in a type-safe way + @HandleBeforeSave + public void handlePersonSave(Person p) { + // … you can now deal with Person in a type-safe way } - @HandleBeforeSave(Profile.class) public void handleProfileSave(Profile p) { - ... you can now deal with Profile in a type-safe way + @HandleBeforeSave + public void handleProfileSave(Profile p) { + // … you can now deal with Profile in a type-safe way } } ---- -You can also declare the domain type at the class level: +The domain type whose events you're interested in is determined from the type of the first parameter of the annotated methods. -[source,java] ----- -@RepositoryEventHandler(Person.class) -public class PersonEventHandler { - - @HandleBeforeSave public void handleBeforeSave(Person p) { - ... - } - - @HandleAfterDelete public void handleAfterDelete(Person p) { - ... - } -} ----- - -Just declare an instance of your annotated bean in your `ApplicationContext` and the `BeanPostProcessor` that is by default created in `RepositoryRestMvcConfiguration` will inspect the bean for handlers and wire them to the correct events. +Just declare an instance of your annotated bean in your `ApplicationContext` and the `BeanPostProcessor` that is by default created in `RepositoryRestMvcConfiguration` will inspect the bean for handlers and wire them to the correct events. [source,java] ---- @Configuration public class RepositoryConfiguration { - @Bean PersonEventHandler personEventHandler() { + @Bean + PersonEventHandler personEventHandler() { return new PersonEventHandler(); } }