From a9ad69f84431e8f2254cc03726675eb6ce41839c Mon Sep 17 00:00:00 2001 From: Markus Geiss Date: Sun, 26 Apr 2015 16:17:44 +0200 Subject: [PATCH 1/2] Added provider to register and retrieve command handlers. --- mifosng-provider/dependencies.gradle | 4 +- mifosng-provider/dev-dependencies.gradle | 4 +- .../commands/annotation/CommandType.java | 33 +++++++++ .../provider/CommandHandlerProvider.java | 72 +++++++++++++++++++ .../SynchronousCommandProcessingService.java | 12 +++- .../handler/CreateClientCommandHandler.java | 7 +- .../provider/CommandHandlerProviderTest.java | 56 +++++++++++++++ .../provider/InvalidCommandHandler.java | 23 ++++++ .../provider/ValidCommandHandler.java | 25 +++++++ 9 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/commands/annotation/CommandType.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/commands/provider/CommandHandlerProvider.java create mode 100644 mifosng-provider/src/test/java/org/mifosplatform/commands/provider/CommandHandlerProviderTest.java create mode 100644 mifosng-provider/src/test/java/org/mifosplatform/commands/provider/InvalidCommandHandler.java create mode 100644 mifosng-provider/src/test/java/org/mifosplatform/commands/provider/ValidCommandHandler.java diff --git a/mifosng-provider/dependencies.gradle b/mifosng-provider/dependencies.gradle index 092847df2..19bf90bdd 100644 --- a/mifosng-provider/dependencies.gradle +++ b/mifosng-provider/dependencies.gradle @@ -81,7 +81,9 @@ dependencies { // Although Mifos (at the time of writing) doesn't have any compile time dep. on this, // it's useful to have this for the Spring Boot TestRestTemplate http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-rest-templates-test-utility - [group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3.5'] + [group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3.5'], + // Once we've switched to Java 8 this dep can be removed. + [group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.0'] ) testCompile 'junit:junit:4.11', 'junit:junit-dep:4.11', diff --git a/mifosng-provider/dev-dependencies.gradle b/mifosng-provider/dev-dependencies.gradle index 573f262d3..aa3aa9a7e 100644 --- a/mifosng-provider/dev-dependencies.gradle +++ b/mifosng-provider/dev-dependencies.gradle @@ -81,7 +81,9 @@ dependencies { // Although Mifos (at the time of writing) doesn't have any compile time dep. on this, // it's useful to have this for the Spring Boot TestRestTemplate http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-rest-templates-test-utility - [group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3.5'] + [group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3.5'], + // Once we've switched to Java 8 this dep can be removed. + [group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.0'] ) testCompile 'junit:junit:4.11', 'junit:junit-dep:4.11', diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/annotation/CommandType.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/annotation/CommandType.java new file mode 100644 index 000000000..23764bc97 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/annotation/CommandType.java @@ -0,0 +1,33 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.commands.annotation; + +import java.lang.annotation.*; + +/** + * Specifies the command type for the annotated class.
+ *
+ * The entity name (e.g. CLIENT, SAVINGSACCOUNT, LOANPRODUCT) and the action (e.g. CREATE, DELETE) must be given. + * + * @author Markus Geiss + * @version 1.0 + * @since 15.06 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +public @interface CommandType { + + /** + * Returns the name of the entity for this {@link CommandType}. + */ + String entity(); + + /** + * Return the name of the action for this {@link CommandType}. + */ + String action(); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/provider/CommandHandlerProvider.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/provider/CommandHandlerProvider.java new file mode 100644 index 000000000..ce6375248 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/provider/CommandHandlerProvider.java @@ -0,0 +1,72 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.commands.provider; + +import com.google.common.base.Preconditions; +import org.apache.commons.lang3.StringUtils; +import org.mifosplatform.commands.annotation.CommandType; +import org.mifosplatform.commands.exception.UnsupportedCommandException; +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.annotation.Nonnull; +import java.util.HashMap; + +/** + * {@link CommandHandlerProvider} provides {@link NewCommandSourceHandler}s for a given entity and action.
+ *
+ * A {@link NewCommandSourceHandler} can be registered and the annotation {@link CommandType} is used to determine + * the entity and the action the handler is capable to process. + * + * @author Markus Geiss + * @version 1.0 + * @since 15.06 + * @see NewCommandSourceHandler + * @see CommandType + */ +@Component +@Scope("singleton") +public class CommandHandlerProvider { + + private final HashMap registeredHandlers = new HashMap<>(); + + CommandHandlerProvider() { + super(); + } + + /** + * Registers a {@link NewCommandSourceHandler} using the annotation {@link CommandType} to register the handler.
+ *
+ * @param handler the {@link NewCommandSourceHandler} to be added, must be given + */ + public void registerHandler(@Nonnull final C handler) { + Preconditions.checkArgument(handler != null, "A handler must be given!"); + + final CommandType commandType = handler.getClass().getAnnotation(CommandType.class); + this.registeredHandlers.put(commandType.entity() + "|" + commandType.action(), handler); + } + + /** + * Returns a handler gor the given entity and action.
+ *
+ * Throws an {@link UnsupportedCommandException} if no handler + * for the given entity, action combination can be found. + * @param entity the entity to lookup the handler, must be given. + * @param action the action to lookup the handler, must be given. + */ + @Nonnull + public NewCommandSourceHandler getHandler (@Nonnull final String entity, @Nonnull final String action) { + Preconditions.checkArgument(StringUtils.isNoneEmpty(entity), "An entity must be given!"); + Preconditions.checkArgument(StringUtils.isNoneEmpty(action), "An action must be given!"); + + final String key = entity + "|" + action; + if (!this.registeredHandlers.containsKey(key)) { + throw new UnsupportedCommandException(key); + } + return this.registeredHandlers.get(entity + "|" + action); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index 9bb20affc..6072e0bd4 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -14,6 +14,7 @@ import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException; import org.mifosplatform.commands.exception.UnsupportedCommandException; import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.commands.provider.CommandHandlerProvider; import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; @@ -38,12 +39,14 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer private final ToApiJsonSerializer toApiResultJsonSerializer; private CommandSourceRepository commandSourceRepository; private final ConfigurationDomainService configurationDomainService; + private final CommandHandlerProvider commandHandlerProvider; @Autowired public SynchronousCommandProcessingService(final PlatformSecurityContext context, final ApplicationContext applicationContext, final ToApiJsonSerializer> toApiJsonSerializer, final ToApiJsonSerializer toApiResultJsonSerializer, - final CommandSourceRepository commandSourceRepository, final ConfigurationDomainService configurationDomainService) { + final CommandSourceRepository commandSourceRepository, final ConfigurationDomainService configurationDomainService, + final CommandHandlerProvider commandHandlerProvider) { this.context = context; this.context = context; this.applicationContext = applicationContext; @@ -52,6 +55,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer this.commandSourceRepository = commandSourceRepository; this.commandSourceRepository = commandSourceRepository; this.configurationDomainService = configurationDomainService; + this.commandHandlerProvider = commandHandlerProvider; } @Transactional @@ -62,6 +66,10 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer final boolean rollbackTransaction = this.configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName()); final NewCommandSourceHandler handler = findCommandHandler(wrapper); + + // TODO remove line 64 and uncomment following statement once all handlers are refactored + // final NewCommandSourceHandler handler = this.commandHandlerProvider.getHandler(wrapper.entityName(), wrapper.actionName()); + final CommandProcessingResult result = handler.processCommand(command); final AppUser maker = this.context.authenticatedUser(wrapper); @@ -124,6 +132,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer .withEntityId(commandSourceResult.getResourceId()).build(); } + // TODO remove this method once all handlers are refactored + @Deprecated private NewCommandSourceHandler findCommandHandler(final CommandWrapper wrapper) { NewCommandSourceHandler handler = null; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/CreateClientCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/CreateClientCommandHandler.java index 921cb3180..17a864746 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/CreateClientCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/CreateClientCommandHandler.java @@ -5,7 +5,9 @@ */ package org.mifosplatform.portfolio.client.handler; +import org.mifosplatform.commands.annotation.CommandType; import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.commands.provider.CommandHandlerProvider; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.portfolio.client.service.ClientWritePlatformService; @@ -14,13 +16,16 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service +@CommandType(entity = "CLIENT", action = "CREATE") public class CreateClientCommandHandler implements NewCommandSourceHandler { private final ClientWritePlatformService clientWritePlatformService; @Autowired - public CreateClientCommandHandler(final ClientWritePlatformService clientWritePlatformService) { + public CreateClientCommandHandler(final ClientWritePlatformService clientWritePlatformService, + final CommandHandlerProvider commandHandlerProvider) { this.clientWritePlatformService = clientWritePlatformService; + commandHandlerProvider.registerHandler(this); } @Transactional diff --git a/mifosng-provider/src/test/java/org/mifosplatform/commands/provider/CommandHandlerProviderTest.java b/mifosng-provider/src/test/java/org/mifosplatform/commands/provider/CommandHandlerProviderTest.java new file mode 100644 index 000000000..132dbb8b1 --- /dev/null +++ b/mifosng-provider/src/test/java/org/mifosplatform/commands/provider/CommandHandlerProviderTest.java @@ -0,0 +1,56 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.commands.provider; + +import org.junit.Assert; +import org.junit.Test; +import org.mifosplatform.commands.exception.UnsupportedCommandException; +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; + +public class CommandHandlerProviderTest { + + public CommandHandlerProviderTest() { + super(); + } + + @Test + public void shouldRegisterHandler() { + final CommandHandlerProvider provider = new CommandHandlerProvider(); + try { + new ValidCommandHandler(provider); + } catch (NullPointerException pex) { + Assert.fail(); + } + + try { + final Long testCommandId = 815L; + + final NewCommandSourceHandler registeredHandler = provider.getHandler("HUMAN", "UPDATE"); + + final CommandProcessingResult result = + registeredHandler.processCommand( + JsonCommand.fromExistingCommand(testCommandId, null, null, null, null, null, null, null, null)); + Assert.assertEquals(testCommandId, result.commandId()); + } catch (UnsupportedCommandException ucex) { + Assert.fail(); + } + } + + @Test(expected = NullPointerException.class) + public void shouldNotRegisterHandlerAndThrowNullPointerException() { + final CommandHandlerProvider provider = new CommandHandlerProvider(); + new InvalidCommandHandler(provider); + } + + @Test(expected = UnsupportedCommandException.class) + public void shouldThrowUnsupportedCommandException() { + final CommandHandlerProvider provider = new CommandHandlerProvider(); + provider.getHandler("WHATEVER", "DOSOMETHING"); + + } +} diff --git a/mifosng-provider/src/test/java/org/mifosplatform/commands/provider/InvalidCommandHandler.java b/mifosng-provider/src/test/java/org/mifosplatform/commands/provider/InvalidCommandHandler.java new file mode 100644 index 000000000..3ff9e6819 --- /dev/null +++ b/mifosng-provider/src/test/java/org/mifosplatform/commands/provider/InvalidCommandHandler.java @@ -0,0 +1,23 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.commands.provider; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; + +public class InvalidCommandHandler implements NewCommandSourceHandler { + + public InvalidCommandHandler(final CommandHandlerProvider commandHandlerProvider) { + super(); + commandHandlerProvider.registerHandler(this); + } + + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return CommandProcessingResult.commandOnlyResult(command.commandId()); + } +} diff --git a/mifosng-provider/src/test/java/org/mifosplatform/commands/provider/ValidCommandHandler.java b/mifosng-provider/src/test/java/org/mifosplatform/commands/provider/ValidCommandHandler.java new file mode 100644 index 000000000..b5c3ef828 --- /dev/null +++ b/mifosng-provider/src/test/java/org/mifosplatform/commands/provider/ValidCommandHandler.java @@ -0,0 +1,25 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.commands.provider; + +import org.mifosplatform.commands.annotation.CommandType; +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; + +@CommandType(entity = "HUMAN", action = "UPDATE") +public class ValidCommandHandler implements NewCommandSourceHandler { + + public ValidCommandHandler(final CommandHandlerProvider commandHandlerProvider) { + super(); + commandHandlerProvider.registerHandler(this); + } + + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return CommandProcessingResult.commandOnlyResult(command.commandId()); + } +} From 06c57000c7fd3408d2974d38f7f61f4f602547ae Mon Sep 17 00:00:00 2001 From: Markus Geiss Date: Sun, 26 Apr 2015 17:08:07 +0200 Subject: [PATCH 2/2] Added autodiscovery for command handlers. --- .../provider/CommandHandlerProvider.java | 45 ++++++++++++------- .../handler/CreateClientCommandHandler.java | 4 +- .../resources/META-INF/spring/appContext.xml | 1 + .../provider/CommandHandlerProviderTest.java | 31 ++++++------- .../provider/InvalidCommandHandler.java | 23 ---------- .../provider/ValidCommandHandler.java | 5 ++- 6 files changed, 48 insertions(+), 61 deletions(-) delete mode 100644 mifosng-provider/src/test/java/org/mifosplatform/commands/provider/InvalidCommandHandler.java diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/provider/CommandHandlerProvider.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/provider/CommandHandlerProvider.java index ce6375248..850e38a81 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/provider/CommandHandlerProvider.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/provider/CommandHandlerProvider.java @@ -6,10 +6,14 @@ package org.mifosplatform.commands.provider; import com.google.common.base.Preconditions; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.mifosplatform.commands.annotation.CommandType; import org.mifosplatform.commands.exception.UnsupportedCommandException; import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -30,28 +34,17 @@ import java.util.HashMap; */ @Component @Scope("singleton") -public class CommandHandlerProvider { +public class CommandHandlerProvider implements ApplicationContextAware { - private final HashMap registeredHandlers = new HashMap<>(); + private ApplicationContext applicationContext; + private HashMap registeredHandlers; CommandHandlerProvider() { super(); } /** - * Registers a {@link NewCommandSourceHandler} using the annotation {@link CommandType} to register the handler.
- *
- * @param handler the {@link NewCommandSourceHandler} to be added, must be given - */ - public void registerHandler(@Nonnull final C handler) { - Preconditions.checkArgument(handler != null, "A handler must be given!"); - - final CommandType commandType = handler.getClass().getAnnotation(CommandType.class); - this.registeredHandlers.put(commandType.entity() + "|" + commandType.action(), handler); - } - - /** - * Returns a handler gor the given entity and action.
+ * Returns a handler for the given entity and action.
*
* Throws an {@link UnsupportedCommandException} if no handler * for the given entity, action combination can be found. @@ -67,6 +60,26 @@ public class CommandHandlerProvider { if (!this.registeredHandlers.containsKey(key)) { throw new UnsupportedCommandException(key); } - return this.registeredHandlers.get(entity + "|" + action); + return (NewCommandSourceHandler)this.applicationContext.getBean(this.registeredHandlers.get(key)); + } + + private void initializeHandlerRegistry() { + if (this.registeredHandlers == null) { + this.registeredHandlers = new HashMap<>(); + + final String[] commandHandlerBeans = this.applicationContext.getBeanNamesForAnnotation(CommandType.class); + if (ArrayUtils.isNotEmpty(commandHandlerBeans)) { + for (final String commandHandlerName : commandHandlerBeans) { + final CommandType commandType = this.applicationContext.getType(commandHandlerName).getAnnotation(CommandType.class); + this.registeredHandlers.put(commandType.entity() + "|" + commandType.action(), commandHandlerName); + } + } + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + this.initializeHandlerRegistry(); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/CreateClientCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/CreateClientCommandHandler.java index 17a864746..3b4526175 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/CreateClientCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/CreateClientCommandHandler.java @@ -22,10 +22,8 @@ public class CreateClientCommandHandler implements NewCommandSourceHandler { private final ClientWritePlatformService clientWritePlatformService; @Autowired - public CreateClientCommandHandler(final ClientWritePlatformService clientWritePlatformService, - final CommandHandlerProvider commandHandlerProvider) { + public CreateClientCommandHandler(final ClientWritePlatformService clientWritePlatformService) { this.clientWritePlatformService = clientWritePlatformService; - commandHandlerProvider.registerHandler(this); } @Transactional diff --git a/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml b/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml index 422d368b4..26e8abead 100644 --- a/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml +++ b/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml @@ -27,6 +27,7 @@