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..850e38a81 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/provider/CommandHandlerProvider.java @@ -0,0 +1,85 @@ +/** + * 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.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; + +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 implements ApplicationContextAware { + + private ApplicationContext applicationContext; + private HashMap registeredHandlers; + + CommandHandlerProvider() { + super(); + } + + /** + * 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. + * @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 (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/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..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 @@ -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,6 +16,7 @@ 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; 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 @@