diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 4295be5f1..e870cd797 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -9091,6 +9091,92 @@ Request Body: { } +   +
+
+

Cache

+

The following settings are possible for cache: +

+

+

By default caching is set to No Caching. Switching between caches results in the cache been clear e.g. from Single node to No cache and back again would clear down the single node cache.

+
+
+ +   +
+
+

Retrieve Cache Types

+

Returns the list of caches.

+

Example Requests:

+
caches
+
+
+ +GET https://DomainName/api/v1/caches + + +[ + { + "cacheType": { + "id": 1, + "code": "cacheType.noCache", + "value": "No cache" + }, + "enabled": true + }, + { + "cacheType": { + "id": 2, + "code": "cacheType.singleNode", + "value": "Single node" + }, + "enabled": false + }, + { + "cacheType": { + "id": 3, + "code": "cacheType.multiNode", + "value": "Multi node" + }, + "enabled": false + } +] + +
+
+ +   +
+
+

Switch Cache

+

Switches the cache to chosen one.

+
+
+ +PUT https://DomainName/api/v1/caches + + +PUT caches +Content-Type: application/json +Request Body: +{ + "cacheType": 2 +} + + +{ + "changes": { + "cacheType": 2 + } +} + +
+
+  
@@ -19359,6 +19445,14 @@ GET https://DomainName/api/v1/search?query=000000111 + + Cache + caches + + List Cache Types + Switch Cache + + Center centers diff --git a/mifosng-db/migrations/core_db/V115__permissions_for_cache_api.sql b/mifosng-db/migrations/core_db/V115__permissions_for_cache_api.sql new file mode 100644 index 000000000..e195d04a9 --- /dev/null +++ b/mifosng-db/migrations/core_db/V115__permissions_for_cache_api.sql @@ -0,0 +1,19 @@ +DROP TABLE IF EXISTS `c_cache`; +CREATE TABLE `c_cache` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `cache_type_enum` tinyint(1) NOT NULL DEFAULT '1', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + +INSERT INTO `c_cache` +(`id`,`cache_type_enum`) +VALUES +(1, 1); + + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('configuration', 'READ_CACHE', 'CACHE', 'READ', 0); + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('configuration', 'UPDATE_CACHE', 'CACHE', 'UPDATE', 0); \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index 864f10867..8b3ede941 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -112,15 +112,15 @@ public class CommandWrapper { } public boolean isCreateDatatable() { - return (this.actionName.equalsIgnoreCase("CREATE") && this.href.startsWith("/datatables/") && this.entityId == null); + return this.actionName.equalsIgnoreCase("CREATE") && this.href.startsWith("/datatables/") && this.entityId == null; } public boolean isDeleteDatatable() { - return (this.actionName.equalsIgnoreCase("DELETE") && this.href.startsWith("/datatables/") && this.entityId == null); + return this.actionName.equalsIgnoreCase("DELETE") && this.href.startsWith("/datatables/") && this.entityId == null; } public boolean isUpdateDatatable() { - return (this.actionName.equalsIgnoreCase("UPDATE") && this.href.startsWith("/datatables/") && this.entityId == null); + return this.actionName.equalsIgnoreCase("UPDATE") && this.href.startsWith("/datatables/") && this.entityId == null; } public String getTaskPermissionName() { @@ -185,8 +185,8 @@ public class CommandWrapper { public boolean isUpdate() { // permissions resource has special update which involves no resource. - return (isPermissionResource() && isUpdateOperation()) || (isCurrencyResource() && isUpdateOperation()) - || (isUpdateOperation() && this.entityId != null); + return isPermissionResource() && isUpdateOperation() || isCurrencyResource() && isUpdateOperation() || isCacheResource() && isUpdateOperation() + || isUpdateOperation() && this.entityId != null; } public boolean isUpdateOperation() { @@ -615,4 +615,8 @@ public class CommandWrapper { public boolean isSaveOrUpdateAttendance() { return this.actionName.equalsIgnoreCase("SAVEORUPDATEATTENDANCE"); } + + public boolean isCacheResource() { + return this.entityName.equalsIgnoreCase("CACHE"); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index 9210db4e2..32ca30133 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -844,7 +844,7 @@ public class CommandWrapperBuilder { this.href = "/savingsaccounts/" + accountId + "?command=activate"; return this; } - + public CommandWrapperBuilder closeSavingsAccountApplication(final Long accountId) { this.actionName = "CLOSE"; this.entityName = "SAVINGSACCOUNT"; @@ -1076,7 +1076,7 @@ public class CommandWrapperBuilder { this.href = "/groups/" + groupId + "?command=assignStaff"; return this; } - + public CommandWrapperBuilder closeGroup(final Long groupId) { this.actionName = "CLOSE"; this.entityName = "GROUP"; @@ -1085,7 +1085,7 @@ public class CommandWrapperBuilder { this.href = "/groups/" + groupId + "?command=close"; return this; } - + public CommandWrapperBuilder createCollateral(final Long loanId) { this.actionName = "CREATE"; this.entityName = "COLLATERAL"; @@ -1162,7 +1162,7 @@ public class CommandWrapperBuilder { this.href = "/centers/" + centerId + "?command=saveCollectionSheet"; return this; } - + public CommandWrapperBuilder closeCenter(final Long centerId) { this.actionName = "CLOSE"; this.entityName = "CENTER"; @@ -1195,7 +1195,7 @@ public class CommandWrapperBuilder { this.href = "/accountingrules/" + accountingRuleId; return this; } - + public CommandWrapperBuilder updateTaxonomyMapping(final Long mappingId) { this.actionName = "UPDATE"; this.entityName = "XBRLMAPPING"; @@ -1331,4 +1331,11 @@ public class CommandWrapperBuilder { this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + entityId + "?command=saveOrUpdateAttendance"; return this; } + + public CommandWrapperBuilder updateCache() { + this.actionName = "UPDATE"; + this.entityName = "CACHE"; + this.href = "/cache"; + return this; + } } \ No newline at end of file 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 7b4daa8f9..daf44520f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -519,7 +519,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer } else if (wrapper.isXBRLMappingResource()) { if (wrapper.isUpdate()) { - handler = applicationContext.getBean("updateTaxonomyMappingCommandHandler", NewCommandSourceHandler.class); + handler = this.applicationContext.getBean("updateTaxonomyMappingCommandHandler", NewCommandSourceHandler.class); } else { throw new UnsupportedCommandException(wrapper.commandName()); } @@ -544,6 +544,12 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer } else { throw new UnsupportedCommandException(wrapper.commandName()); } + } else if (wrapper.isCacheResource()) { + if (wrapper.isUpdateOperation()) { + handler = this.applicationContext.getBean("updateCacheCommandHandler", NewCommandSourceHandler.class); + } else { + throw new UnsupportedCommandException(wrapper.commandName()); + } } else if (wrapper.isMeetingResource()) { if (wrapper.isCreate()) { handler = this.applicationContext.getBean("createMeetingCommandHandler", NewCommandSourceHandler.class); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/CacheApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/CacheApiConstants.java new file mode 100644 index 000000000..361c6c6d5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/CacheApiConstants.java @@ -0,0 +1,13 @@ +package org.mifosplatform.infrastructure.cache; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class CacheApiConstants { + + public static final String RESOURCE_NAME = "CACHE"; + public static final String cacheTypeParameter = "cacheType"; + public static final Set REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(cacheTypeParameter)); + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/CacheEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/CacheEnumerations.java new file mode 100644 index 000000000..ef916a011 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/CacheEnumerations.java @@ -0,0 +1,37 @@ +/** + * 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.infrastructure.cache; + +import org.mifosplatform.infrastructure.cache.domain.CacheType; +import org.mifosplatform.infrastructure.core.data.EnumOptionData; + +public class CacheEnumerations { + + public static EnumOptionData cacheType(final int id) { + return cacheType(CacheType.fromInt(id)); + } + + public static EnumOptionData cacheType(final CacheType cacheType) { + EnumOptionData optionData = new EnumOptionData(CacheType.INVALID.getValue().longValue(), CacheType.INVALID.getCode(), + "Invalid"); + switch (cacheType) { + case INVALID: + optionData = new EnumOptionData(CacheType.INVALID.getValue().longValue(), CacheType.INVALID.getCode(), "Invalid"); + break; + case NO_CACHE: + optionData = new EnumOptionData(CacheType.NO_CACHE.getValue().longValue(), CacheType.NO_CACHE.getCode(), "No cache"); + break; + case SINGLE_NODE: + optionData = new EnumOptionData(CacheType.SINGLE_NODE.getValue().longValue(), CacheType.SINGLE_NODE.getCode(), "Single node"); + break; + case MULTI_NODE: + optionData = new EnumOptionData(CacheType.MULTI_NODE.getValue().longValue(), CacheType.MULTI_NODE.getCode(), "Multi node"); + break; + } + + return optionData; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/PlatformCacheConfiguration.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/PlatformCacheConfiguration.java index e2f6c555d..c2f1520ca 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/PlatformCacheConfiguration.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/PlatformCacheConfiguration.java @@ -1,5 +1,6 @@ package org.mifosplatform.infrastructure.cache; +import org.mifosplatform.infrastructure.cache.service.RuntimeDelegatingCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurer; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/RuntimeDelegatingCacheManager.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/RuntimeDelegatingCacheManager.java deleted file mode 100644 index 7b26b555e..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/RuntimeDelegatingCacheManager.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.mifosplatform.infrastructure.cache; - -import java.util.Collection; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.cache.ehcache.EhCacheCacheManager; -import org.springframework.cache.support.NoOpCacheManager; -import org.springframework.stereotype.Component; - -/** - * At present this implementation of {@link CacheManager} just delegates to the - * real {@link CacheManager} to use. - * - * By default it is {@link NoOpCacheManager} but we can change that by checking - * some persisted configuration in the database on startup and allow user to - * switch implementation through UI/API - */ -@Component -public class RuntimeDelegatingCacheManager implements CacheManager { - - private final CacheManager ehcacheCacheManager; - private final CacheManager noOpCacheManager = new NoOpCacheManager(); - private CacheManager currentCacheManager = this.noOpCacheManager; - - @Autowired - public RuntimeDelegatingCacheManager(final EhCacheCacheManager ehCacheCacheManager) { - this.ehcacheCacheManager = ehCacheCacheManager; - } - - @Override - public Cache getCache(final String name) { - return this.currentCacheManager.getCache(name); - } - - @Override - public Collection getCacheNames() { - return this.currentCacheManager.getCacheNames(); - } - - // TODO - Annuruddha - you need to create API to call to switch caching - // approach from NoOp to Single instance Echache - we can tackle the - // distributed cache through memcache then - didnt see the configuration for - // that. - // example method to switch caching mechanism used - private void switchToEhCache() { - this.currentCacheManager = this.ehcacheCacheManager; - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/api/CacheApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/api/CacheApiResource.java new file mode 100644 index 000000000..ef4ec4a83 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/api/CacheApiResource.java @@ -0,0 +1,84 @@ +/** + * 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.infrastructure.cache.api; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import org.mifosplatform.commands.domain.CommandWrapper; +import org.mifosplatform.commands.service.CommandWrapperBuilder; +import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; +import org.mifosplatform.infrastructure.cache.data.CacheData; +import org.mifosplatform.infrastructure.cache.service.RuntimeDelegatingCacheManager; +import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/caches") +@Consumes({ MediaType.APPLICATION_JSON }) +@Produces({ MediaType.APPLICATION_JSON }) +@Component +@Scope("singleton") +public class CacheApiResource { + + private final Set RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList("id")); + private final String resourceNameForPermissions = "CACHE"; + + private final PlatformSecurityContext context; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final RuntimeDelegatingCacheManager cacheService; + + @Autowired + public CacheApiResource(final PlatformSecurityContext context, @Qualifier("runtimeDelegatingCacheManager") final RuntimeDelegatingCacheManager cacheService, + final DefaultToApiJsonSerializer toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + this.context = context; + this.cacheService = cacheService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.apiRequestParameterHelper = apiRequestParameterHelper; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + } + + @GET + public String retrieveAll(@Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + final Collection codes = this.cacheService.retrieveAll(); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, codes, this.RESPONSE_DATA_PARAMETERS); + } + + @PUT + public String switchCache(final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCache().withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/command/UpdateCacheCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/command/UpdateCacheCommandHandler.java new file mode 100644 index 000000000..e8b6db91f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/command/UpdateCacheCommandHandler.java @@ -0,0 +1,68 @@ +/** + * 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.infrastructure.cache.command; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.cache.CacheApiConstants; +import org.mifosplatform.infrastructure.cache.domain.CacheType; +import org.mifosplatform.infrastructure.cache.service.CacheWritePlatformService; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.google.gson.reflect.TypeToken; + +@Service +public class UpdateCacheCommandHandler implements NewCommandSourceHandler { + + private final CacheWritePlatformService cacheService; + + @Autowired + public UpdateCacheCommandHandler(final CacheWritePlatformService cacheService) { + this.cacheService = cacheService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + final String json = command.json(); + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + command.checkForUnsupportedParameters(typeOfMap, json, CacheApiConstants.REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(CacheApiConstants.RESOURCE_NAME.toLowerCase()); + + final int cacheTypeEnum = command.integerValueSansLocaleOfParameterNamed(CacheApiConstants.cacheTypeParameter); + baseDataValidator.reset().parameter(CacheApiConstants.cacheTypeParameter).value(Integer.valueOf(cacheTypeEnum)).notNull() + .isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + + final CacheType cacheType = CacheType.fromInt(cacheTypeEnum); + + final Map changes = this.cacheService.switchToCache(cacheType); + + return new CommandProcessingResultBuilder().withCommandId(command.commandId()).with(changes).build(); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/data/CacheData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/data/CacheData.java new file mode 100644 index 000000000..8e355b033 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/data/CacheData.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.infrastructure.cache.data; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; + +public class CacheData { + + @SuppressWarnings("unused") + private final EnumOptionData cacheType; + @SuppressWarnings("unused") + private final boolean enabled; + + public static CacheData instance(final EnumOptionData cacheType, final boolean enabled) { + return new CacheData(cacheType, enabled); + } + + private CacheData(final EnumOptionData cacheType, final boolean enabled) { + this.cacheType = cacheType; + this.enabled = enabled; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/domain/CacheType.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/domain/CacheType.java new file mode 100644 index 000000000..2986b0cb7 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/domain/CacheType.java @@ -0,0 +1,66 @@ +/** + * 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.infrastructure.cache.domain; + +import java.util.HashMap; +import java.util.Map; + +public enum CacheType { + + INVALID(0, "cacheType.invalid"), // + NO_CACHE(1, "cacheType.noCache"), // + SINGLE_NODE(2, "cacheType.singleNode"), // + MULTI_NODE(3, "cacheType.multiNode"); + + private final Integer value; + private final String code; + + private static final Map intToEnumMap = new HashMap(); + + static { + for (final CacheType type : CacheType.values()) { + intToEnumMap.put(type.value, type); + } + } + + public static CacheType fromInt(final Integer value) { + CacheType type = intToEnumMap.get(value); + if (type == null) { + type = INVALID; + } + return type; + } + + private CacheType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + @Override + public String toString() { + return name().toString().replaceAll("_", " "); + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return this.code; + } + + public boolean isNoCache() { + return NO_CACHE.getValue().equals(this.value); + } + + public boolean isEhcache() { + return SINGLE_NODE.getValue().equals(this.value); + } + + public boolean isDistributedCache() { + return MULTI_NODE.getValue().equals(this.value); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/domain/PlatformCache.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/domain/PlatformCache.java new file mode 100644 index 000000000..1899b00f9 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/domain/PlatformCache.java @@ -0,0 +1,44 @@ +/** + * 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.infrastructure.cache.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "c_cache") +public class PlatformCache extends AbstractPersistable { + + @Column(name = "cache_type_enum") + private Integer cacheType; + + protected PlatformCache() { + this.cacheType = null; + } + + public PlatformCache(final CacheType cacheType) { + this.cacheType = cacheType.getValue(); + } + + public boolean isNoCachedEnabled() { + return CacheType.fromInt(this.cacheType).isNoCache(); + } + + public boolean isEhcacheEnabled() { + return CacheType.fromInt(this.cacheType).isEhcache(); + } + + public boolean isDistributedCacheEnabled() { + return CacheType.fromInt(this.cacheType).isDistributedCache(); + } + + public void update(final CacheType cacheType) { + this.cacheType = cacheType.getValue(); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/domain/PlatformCacheRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/domain/PlatformCacheRepository.java new file mode 100644 index 000000000..86a965a43 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/domain/PlatformCacheRepository.java @@ -0,0 +1,13 @@ +/** + * 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.infrastructure.cache.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface PlatformCacheRepository extends JpaRepository, JpaSpecificationExecutor { + // +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/service/CacheWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/service/CacheWritePlatformService.java new file mode 100644 index 000000000..b345deee5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/service/CacheWritePlatformService.java @@ -0,0 +1,15 @@ +/** + * 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.infrastructure.cache.service; + +import java.util.Map; + +import org.mifosplatform.infrastructure.cache.domain.CacheType; + +public interface CacheWritePlatformService { + + Map switchToCache(CacheType cacheType); +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/service/CacheWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/service/CacheWritePlatformServiceJpaRepositoryImpl.java new file mode 100644 index 000000000..e73a6c1c4 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/service/CacheWritePlatformServiceJpaRepositoryImpl.java @@ -0,0 +1,44 @@ +/** + * 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.infrastructure.cache.service; + +import java.util.Map; + +import org.mifosplatform.infrastructure.cache.domain.CacheType; +import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CacheWritePlatformServiceJpaRepositoryImpl implements CacheWritePlatformService { + + private final ConfigurationDomainService configurationDomainService; + private final RuntimeDelegatingCacheManager cacheService; + + @Autowired + public CacheWritePlatformServiceJpaRepositoryImpl(final ConfigurationDomainService configurationDomainService, + @Qualifier("runtimeDelegatingCacheManager") final RuntimeDelegatingCacheManager cacheService) { + this.configurationDomainService = configurationDomainService; + this.cacheService = cacheService; + } + + @Transactional + @Override + public Map switchToCache(final CacheType toCacheType) { + + final boolean ehCacheEnabled = this.configurationDomainService.isEhcacheEnabled(); + + final Map changes = this.cacheService.switchToCache(ehCacheEnabled, toCacheType); + + if (!changes.isEmpty()) { + this.configurationDomainService.updateCache(toCacheType); + } + + return changes; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/service/RuntimeDelegatingCacheManager.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/service/RuntimeDelegatingCacheManager.java new file mode 100644 index 000000000..54612b0ae --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/cache/service/RuntimeDelegatingCacheManager.java @@ -0,0 +1,104 @@ +package org.mifosplatform.infrastructure.cache.service; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.mifosplatform.infrastructure.cache.CacheApiConstants; +import org.mifosplatform.infrastructure.cache.CacheEnumerations; +import org.mifosplatform.infrastructure.cache.data.CacheData; +import org.mifosplatform.infrastructure.cache.domain.CacheType; +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.ehcache.EhCacheCacheManager; +import org.springframework.cache.support.NoOpCacheManager; +import org.springframework.stereotype.Component; + +/** + * At present this implementation of {@link CacheManager} just delegates to the + * real {@link CacheManager} to use. + * + * By default it is {@link NoOpCacheManager} but we can change that by checking + * some persisted configuration in the database on startup and allow user to + * switch implementation through UI/API + */ +@Component(value = "runtimeDelegatingCacheManager") +public class RuntimeDelegatingCacheManager implements CacheManager { + + private final EhCacheCacheManager ehcacheCacheManager; + private final CacheManager noOpCacheManager = new NoOpCacheManager(); + private CacheManager currentCacheManager = this.noOpCacheManager; + + @Autowired + public RuntimeDelegatingCacheManager(final EhCacheCacheManager ehCacheCacheManager) { + this.ehcacheCacheManager = ehCacheCacheManager; + } + + @Override + public Cache getCache(final String name) { + return this.currentCacheManager.getCache(name); + } + + @Override + public Collection getCacheNames() { + return this.currentCacheManager.getCacheNames(); + } + + public Collection retrieveAll() { + + final boolean ehcacheEnabled = false; + final boolean noCacheEnabled = !ehcacheEnabled; + final boolean distributedCacheEnabled = false; + + final EnumOptionData noCacheType = CacheEnumerations.cacheType(CacheType.NO_CACHE); + final EnumOptionData singleNodeCacheType = CacheEnumerations.cacheType(CacheType.SINGLE_NODE); + final EnumOptionData multiNodeCacheType = CacheEnumerations.cacheType(CacheType.MULTI_NODE); + + final CacheData noCache = CacheData.instance(noCacheType, noCacheEnabled); + final CacheData singleNodeCache = CacheData.instance(singleNodeCacheType, ehcacheEnabled); + final CacheData distributedCache = CacheData.instance(multiNodeCacheType, distributedCacheEnabled); + + final Collection caches = Arrays.asList(noCache, singleNodeCache, distributedCache); + return caches; + } + + public Map switchToCache(final boolean ehcacheEnabled, final CacheType toCacheType) { + + final Map changes = new HashMap(); + + final boolean noCacheEnabled = !ehcacheEnabled; + final boolean distributedCacheEnabled = !ehcacheEnabled; + + switch (toCacheType) { + case INVALID: + break; + case NO_CACHE: + if (!noCacheEnabled) { + changes.put(CacheApiConstants.cacheTypeParameter, toCacheType.getValue()); + this.currentCacheManager = this.noOpCacheManager; + } + break; + case SINGLE_NODE: + if (!ehcacheEnabled) { + changes.put(CacheApiConstants.cacheTypeParameter, toCacheType.getValue()); + clearEhCache(); + this.currentCacheManager = this.ehcacheCacheManager; + } + break; + case MULTI_NODE: + if (!distributedCacheEnabled) { + changes.put(CacheApiConstants.cacheTypeParameter, toCacheType.getValue()); + } + break; + } + + return changes; + } + + private void clearEhCache() { + this.ehcacheCacheManager.getCacheManager().clearAll(); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java index 191926716..f205bb537 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.infrastructure.configuration.domain; +import org.mifosplatform.infrastructure.cache.domain.CacheType; + public interface ConfigurationDomainService { boolean isMakerCheckerEnabledForTask(String taskPermissionCode); @@ -14,4 +16,6 @@ public interface ConfigurationDomainService { boolean allowTransactionsOnHolidayEnabled(); boolean allowTransactionsOnNonWorkingDayEnabled(); boolean isConstraintApproachEnabledForDatatables(); + boolean isEhcacheEnabled(); + void updateCache(CacheType cacheType); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index 11c3f8ff2..9ca5cd2e8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -6,24 +6,31 @@ package org.mifosplatform.infrastructure.configuration.domain; import org.apache.commons.lang.StringUtils; +import org.mifosplatform.infrastructure.cache.domain.CacheType; +import org.mifosplatform.infrastructure.cache.domain.PlatformCacheRepository; +import org.mifosplatform.infrastructure.cache.domain.PlatformCache; import org.mifosplatform.infrastructure.configuration.exception.GlobalConfigurationPropertyNotFoundException; import org.mifosplatform.useradministration.domain.Permission; import org.mifosplatform.useradministration.domain.PermissionRepository; import org.mifosplatform.useradministration.exception.PermissionNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class ConfigurationDomainServiceJpa implements ConfigurationDomainService { private final PermissionRepository permissionRepository; private final GlobalConfigurationRepository globalConfigurationRepository; + private final PlatformCacheRepository cacheTypeRepository; @Autowired public ConfigurationDomainServiceJpa(final PermissionRepository permissionRepository, - final GlobalConfigurationRepository globalConfigurationRepository) { + final GlobalConfigurationRepository globalConfigurationRepository, + final PlatformCacheRepository cacheTypeRepository) { this.permissionRepository = permissionRepository; this.globalConfigurationRepository = globalConfigurationRepository; + this.cacheTypeRepository = cacheTypeRepository; } @Override @@ -87,4 +94,18 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService if (property == null) { throw new GlobalConfigurationPropertyNotFoundException(colunnValue); } return property.isEnabled(); } + + @Override + public boolean isEhcacheEnabled() { + return this.cacheTypeRepository.findOne(Long.valueOf(1)).isEhcacheEnabled(); + } + + @Transactional + @Override + public void updateCache(final CacheType cacheType) { + final PlatformCache cache = this.cacheTypeRepository.findOne(Long.valueOf(1)); + cache.update(cacheType); + + this.cacheTypeRepository.save(cache); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java index d6c81ed8a..cda3deb09 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java @@ -5,10 +5,12 @@ */ package org.mifosplatform.infrastructure.core.api; +import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.Arrays; import java.util.Date; import java.util.Locale; +import java.util.Set; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; @@ -24,7 +26,7 @@ import com.google.gson.JsonElement; /** * Immutable representation of a command. - * + * * Wraps the provided JSON with convenience functions for extracting parameter * values and checking for changes against an existing value. */ @@ -97,7 +99,7 @@ public final class JsonCommand { public String jsonFragment(final String paramName) { String jsonFragment = null; if (this.parsedCommand.getAsJsonObject().has(paramName)) { - JsonElement fragment = this.parsedCommand.getAsJsonObject().get(paramName); + final JsonElement fragment = this.parsedCommand.getAsJsonObject().get(paramName); jsonFragment = this.fromApiJsonHelper.toJson(fragment); } return jsonFragment; @@ -234,7 +236,7 @@ public final class JsonCommand { } public boolean parameterExists(final String parameterName) { - return this.fromApiJsonHelper.parameterExists(parameterName, parsedCommand); + return this.fromApiJsonHelper.parameterExists(parameterName, this.parsedCommand); } public boolean hasParameter(final String parameterName) { @@ -259,7 +261,7 @@ public final class JsonCommand { } public Long longValueOfParameterNamed(final String parameterName) { - return this.fromApiJsonHelper.extractLongNamed(parameterName, parsedCommand); + return this.fromApiJsonHelper.extractLongNamed(parameterName, this.parsedCommand); } public boolean isChangeInDateParameterNamed(final String parameterName, final Date existingValue) { @@ -280,15 +282,15 @@ public final class JsonCommand { } public LocalDate localDateValueOfParameterNamed(final String parameterName) { - return this.fromApiJsonHelper.extractLocalDateNamed(parameterName, parsedCommand); + return this.fromApiJsonHelper.extractLocalDateNamed(parameterName, this.parsedCommand); } public MonthDay extractMonthDayNamed(final String parameterName) { - return this.fromApiJsonHelper.extractMonthDayNamed(parameterName, parsedCommand); + return this.fromApiJsonHelper.extractMonthDayNamed(parameterName, this.parsedCommand); } public Date DateValueOfParameterNamed(final String parameterName) { - LocalDate localDate = this.fromApiJsonHelper.extractLocalDateNamed(parameterName, parsedCommand); + final LocalDate localDate = this.fromApiJsonHelper.extractLocalDateNamed(parameterName, this.parsedCommand); if (localDate == null) { return null; } return localDate.toDateMidnight().toDate(); } @@ -303,12 +305,12 @@ public final class JsonCommand { } public String stringValueOfParameterNamed(final String parameterName) { - final String value = this.fromApiJsonHelper.extractStringNamed(parameterName, parsedCommand); + final String value = this.fromApiJsonHelper.extractStringNamed(parameterName, this.parsedCommand); return StringUtils.defaultIfEmpty(value, ""); } - + public String stringValueOfParameterNamedAllowingNull(final String parameterName) { - return this.fromApiJsonHelper.extractStringNamed(parameterName, parsedCommand); + return this.fromApiJsonHelper.extractStringNamed(parameterName, this.parsedCommand); } public boolean isChangeInBigDecimalParameterNamedDefaultingZeroToNull(final String parameterName, final BigDecimal existingValue) { @@ -359,7 +361,7 @@ public final class JsonCommand { } public BigDecimal bigDecimalValueOfParameterNamed(final String parameterName) { - return this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(parameterName, parsedCommand); + return this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(parameterName, this.parsedCommand); } public BigDecimal bigDecimalValueOfParameterNamedDefaultToNullIfZero(final String parameterName) { @@ -398,7 +400,7 @@ public final class JsonCommand { } public Integer integerValueOfParameterNamed(final String parameterName) { - return this.fromApiJsonHelper.extractIntegerWithLocaleNamed(parameterName, parsedCommand); + return this.fromApiJsonHelper.extractIntegerWithLocaleNamed(parameterName, this.parsedCommand); } public Integer integerValueOfParameterNamedDefaultToNullIfZero(final String parameterName) { @@ -415,7 +417,7 @@ public final class JsonCommand { } public Integer integerValueSansLocaleOfParameterNamed(final String parameterName) { - return this.fromApiJsonHelper.extractIntegerSansLocaleNamed(parameterName, parsedCommand); + return this.fromApiJsonHelper.extractIntegerSansLocaleNamed(parameterName, this.parsedCommand); } public boolean isChangeInBooleanParameterNamed(final String parameterName, final Boolean existingValue) { @@ -431,14 +433,14 @@ public final class JsonCommand { * Returns {@link Boolean} that could possibly be null. */ public Boolean booleanObjectValueOfParameterNamed(final String parameterName) { - return this.fromApiJsonHelper.extractBooleanNamed(parameterName, parsedCommand); + return this.fromApiJsonHelper.extractBooleanNamed(parameterName, this.parsedCommand); } /** * always returns true or false */ public boolean booleanPrimitiveValueOfParameterNamed(final String parameterName) { - final Boolean value = this.fromApiJsonHelper.extractBooleanNamed(parameterName, parsedCommand); + final Boolean value = this.fromApiJsonHelper.extractBooleanNamed(parameterName, this.parsedCommand); return (Boolean) ObjectUtils.defaultIfNull(value, Boolean.FALSE); } @@ -452,11 +454,11 @@ public final class JsonCommand { } public String[] arrayValueOfParameterNamed(final String parameterName) { - return this.fromApiJsonHelper.extractArrayNamed(parameterName, parsedCommand); + return this.fromApiJsonHelper.extractArrayNamed(parameterName, this.parsedCommand); } public JsonArray arrayOfParameterNamed(final String parameterName) { - return this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, parsedCommand); + return this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, this.parsedCommand); } public boolean isChangeInPasswordParameterNamed(final String parameterName, final String existingValue, @@ -480,4 +482,8 @@ public final class JsonCommand { public Locale extractLocale() { return this.fromApiJsonHelper.extractLocaleParameter(this.parsedCommand.getAsJsonObject()); } + + public void checkForUnsupportedParameters(final Type typeOfMap, final String json, final Set requestDataParameters) { + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, requestDataParameters); + } } \ No newline at end of file