diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/EntityInterceptor.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/EntityInterceptor.java new file mode 100644 index 000000000..b6fff3f91 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/EntityInterceptor.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +/** + * Interface for the entity interceptor lifecycle. + */ +public interface EntityInterceptor { + + /** + * Callback for the {@link @PrePersist} lifecycle event. + * + * @param entity + * the model entity + */ + default void prePersist(final Object entity) { + }; + + /** + * Callback for the {@link @PostPersist} lifecycle event. + * + * @param entity + * the model entity + */ + default void postPersist(final Object entity) { + }; + + /** + * Callback for the {@link @PostRemove} lifecycle event. + * + * @param entity + * the model entity + */ + default void postRemove(final Object entity) { + }; + + /** + * Callback for the {@link @PreRemove} lifecycle event. + * + * @param entity + * the model entity + */ + default void preRemove(final Object entity) { + }; + + /** + * Callback for the {@link @PostLoad} lifecycle event. + * + * @param entity + * the model entity + */ + default void postLoad(final Object entity) { + }; + + /** + * Callback for the {@link @PreUpdate} lifecycle event. + * + * @param entity + * the model entity + */ + default void preUpdate(final Object entity) { + }; + + /** + * Callback for the {@link @PostUpdate} lifecycle event. + * + * @param entity + * the model entity + */ + default void postUpdate(final Object entity) { + }; + +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java index 7236cbe79..f06d44516 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java @@ -46,6 +46,7 @@ import org.eclipse.hawkbit.repository.jpa.aspects.ExceptionMappingAspectHandler; import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager; import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.CacheManagerHolder; +import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.SystemManagementHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.SystemSecurityContextHolder; @@ -144,6 +145,14 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { return SecurityTokenGeneratorHolder.getInstance(); } + /** + * @return the singleton instance of the {@link EntityInterceptorHolder} + */ + @Bean + public EntityInterceptorHolder entityInterceptorHolder() { + return EntityInterceptorHolder.getInstance(); + } + /** * @return the singleton instance of the {@link CacheManagerHolder} */ diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java index 257828c6f..ca8ac60df 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java @@ -31,7 +31,8 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; */ @MappedSuperclass @Access(AccessType.FIELD) -@EntityListeners({ AuditingEntityListener.class, CacheFieldEntityListener.class, EntityPropertyChangeListener.class }) +@EntityListeners({ AuditingEntityListener.class, CacheFieldEntityListener.class, EntityPropertyChangeListener.class, + EntityInterceptorListener.class }) public abstract class AbstractJpaBaseEntity implements BaseEntity { private static final long serialVersionUID = 1L; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/EntityInterceptorListener.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/EntityInterceptorListener.java new file mode 100644 index 000000000..1e9c39237 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/EntityInterceptorListener.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.jpa.model; + +import java.util.function.Consumer; + +import javax.persistence.PostLoad; +import javax.persistence.PostPersist; +import javax.persistence.PostRemove; +import javax.persistence.PostUpdate; +import javax.persistence.PrePersist; +import javax.persistence.PreRemove; +import javax.persistence.PreUpdate; + +import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder; +import org.eclipse.hawkbit.repository.model.EntityInterceptor; + +/** + * Entity listener which calls the callback's of all registered entity + * interceptors. + */ +public class EntityInterceptorListener { + + @PrePersist + protected void prePersist(final Object entity) { + notifyAll(interceptor -> interceptor.prePersist(entity)); + } + + @PostPersist + protected void postPersist(final Object entity) { + notifyAll(interceptor -> interceptor.postPersist(entity)); + } + + @PostRemove + protected void postRemove(final Object entity) { + notifyAll(interceptor -> interceptor.postRemove(entity)); + } + + @PreRemove + protected void preRemove(final Object entity) { + notifyAll(interceptor -> interceptor.preRemove(entity)); + } + + @PostLoad + protected void postLoad(final Object entity) { + notifyAll(interceptor -> interceptor.postLoad(entity)); + } + + @PreUpdate + protected void preUpdate(final Object entity) { + notifyAll(interceptor -> interceptor.preUpdate(entity)); + } + + @PostUpdate + protected void postUpdate(final Object entity) { + notifyAll(interceptor -> interceptor.postUpdate(entity)); + } + + private void notifyAll(final Consumer action) { + EntityInterceptorHolder.getInstance().getEntityInterceptors().forEach(action); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/helper/EntityInterceptorHolder.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/helper/EntityInterceptorHolder.java new file mode 100644 index 000000000..c374f219a --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/helper/EntityInterceptorHolder.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.jpa.model.helper; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.repository.model.EntityInterceptor; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A singleton bean which holds the {@link EntityInterceptor} to have all + * interceptors in spring beans. + * + */ +public final class EntityInterceptorHolder { + + private static final EntityInterceptorHolder SINGLETON = new EntityInterceptorHolder(); + + @Autowired(required = false) + private final List entityInterceptors = new ArrayList<>(); + + private EntityInterceptorHolder() { + + } + + /** + * @return the entity intreceptor holder singleton instance + */ + public static EntityInterceptorHolder getInstance() { + return SINGLETON; + } + + public List getEntityInterceptors() { + return entityInterceptors; + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/model/EntityInterceptorListenerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/model/EntityInterceptorListenerTest.java new file mode 100644 index 000000000..c3db7ab76 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/model/EntityInterceptorListenerTest.java @@ -0,0 +1,186 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.jpa.model; + +import static org.fest.assertions.Assertions.assertThat; + +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder; +import org.eclipse.hawkbit.repository.model.EntityInterceptor; +import org.eclipse.hawkbit.repository.model.SoftwareModuleType; +import org.eclipse.hawkbit.repository.model.Target; +import org.junit.Test; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +/** + * Test the entity listener interceptor. + */ +@Features("Component Tests - Repository") +@Stories("Entity Listener Interceptor") +public class EntityInterceptorListenerTest extends AbstractJpaIntegrationTest { + + @Override + public void after() { + EntityInterceptorHolder.getInstance().getEntityInterceptors().clear(); + super.after(); + } + + @Test + @Description("Verfies that the pre persist is called after a entity creation.") + public void prePersistIsCalledWhenPersistingATarget() { + executePersistAndAssertCallbackResult(new PrePersistEntityListener()); + } + + @Test + @Description("Verfies that the post persist is called after a entity creation.") + public void postPersistIsCalledWhenPersistingATarget() { + executePersistAndAssertCallbackResult(new PostPersistEntityListener()); + } + + @Test + @Description("Verfies that the post load is called after a entity is loaded.") + public void postLoadIsCalledWhenLoadATarget() { + final PostLoadEntityListener postLoadEntityListener = new PostLoadEntityListener(); + EntityInterceptorHolder.getInstance().getEntityInterceptors().add(postLoadEntityListener); + + final Target targetToBeCreated = entityFactory.generateTarget("targetToBeCreated"); + + targetManagement.createTarget(targetToBeCreated); + + final Target loadedTarget = targetManagement.findTargetByControllerID(targetToBeCreated.getControllerId()); + assertThat(postLoadEntityListener.getEntity()).isNotNull(); + assertThat(postLoadEntityListener.getEntity()).isEqualTo(loadedTarget); + } + + @Test + @Description("Verfies that the pre update is called after a entity update.") + public void preUpdateIsCalledWhenUpdateATarget() { + executeUpdateAndAssertCallbackResult(new PreUpdateEntityListener()); + } + + @Test + @Description("Verfies that the post update is called after a entity update.") + public void postUpdateIsCalledWhenUpdateATarget() { + executeUpdateAndAssertCallbackResult(new PostUpdateEntityListener()); + } + + @Test + @Description("Verfies that the pre remove is called after a entity deletion.") + public void preRemoveIsCalledWhenDeletingATarget() { + executeDeleteAndAssertCallbackResult(new PreRemoveEntityListener()); + } + + @Test + @Description("Verfies that the post remove is called after a entity deletion.") + public void postRemoveIsCalledWhenDeletingATarget() { + executeDeleteAndAssertCallbackResult(new PostRemoveEntityListener()); + } + + private void executePersistAndAssertCallbackResult(final AbstractEntityListener entityInterceptor) { + final Target targetToBeCreated = entityFactory.generateTarget("targetToBeCreated"); + addListenerAndCreateTarget(entityInterceptor, targetToBeCreated); + + assertThat(entityInterceptor.getEntity()).isNotNull(); + assertThat(entityInterceptor.getEntity()).isEqualTo(targetToBeCreated); + } + + private void executeUpdateAndAssertCallbackResult(final AbstractEntityListener entityInterceptor) { + Target updateTarget = addListenerAndCreateTarget(entityInterceptor, + entityFactory.generateTarget("targetToBeCreated")); + updateTarget.setDescription("New"); + + updateTarget = targetManagement.updateTarget(updateTarget); + + assertThat(entityInterceptor.getEntity()).isNotNull(); + assertThat(entityInterceptor.getEntity()).isEqualTo(updateTarget); + } + + private void executeDeleteAndAssertCallbackResult(final AbstractEntityListener entityInterceptor) { + EntityInterceptorHolder.getInstance().getEntityInterceptors().add(entityInterceptor); + final SoftwareModuleType type = softwareManagement + .createSoftwareModuleType(entityFactory.generateSoftwareModuleType("test", "test", "test", 1)); + + softwareManagement.deleteSoftwareModuleType(type); + assertThat(entityInterceptor.getEntity()).isNotNull(); + assertThat(entityInterceptor.getEntity()).isEqualTo(type); + } + + private Target addListenerAndCreateTarget(final AbstractEntityListener entityInterceptor, + final Target targetToBeCreated) { + EntityInterceptorHolder.getInstance().getEntityInterceptors().add(entityInterceptor); + return targetManagement.createTarget(targetToBeCreated); + } + + private static abstract class AbstractEntityListener implements EntityInterceptor { + + private Object entity; + + public Object getEntity() { + return entity; + } + + public void setEntity(final Object entity) { + this.entity = entity; + } + } + + private static class PrePersistEntityListener extends AbstractEntityListener { + @Override + public void prePersist(final Object entity) { + setEntity(entity); + } + } + + private static class PostPersistEntityListener extends AbstractEntityListener { + @Override + public void postPersist(final Object entity) { + setEntity(entity); + } + + } + + private static class PostLoadEntityListener extends AbstractEntityListener { + @Override + public void postLoad(final Object entity) { + setEntity(entity); + } + + } + + private static class PreUpdateEntityListener extends AbstractEntityListener { + @Override + public void preUpdate(final Object entity) { + setEntity(entity); + } + } + + private static class PostUpdateEntityListener extends AbstractEntityListener { + @Override + public void postUpdate(final Object entity) { + setEntity(entity); + } + } + + private static class PreRemoveEntityListener extends AbstractEntityListener { + @Override + public void preRemove(final Object entity) { + setEntity(entity); + } + } + + private static class PostRemoveEntityListener extends AbstractEntityListener { + @Override + public void postRemove(final Object entity) { + setEntity(entity); + } + } +} \ No newline at end of file