MIFOSX-259: support runtime switching of cache through API. By default no-cache implementation is used.

This commit is contained in:
Keith Woodlock
2013-09-16 17:29:36 +01:00
parent 658d38a3fa
commit 347b618be1
21 changed files with 704 additions and 79 deletions
+94
View File
@@ -9091,6 +9091,92 @@ Request Body: { }
</div>
</div>
<a id="cache" name="cache" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h3>Cache</h3>
<p>The following settings are possible for cache:
<ul>
<li><b>No Caching</b>: caching turned off</li>
<li><b>Single node</b>: caching on for single instance deployments of platorm (works for multiple tenants but only one tomcat)</li>
</ul>
</p>
<p>By default caching is set to <i>No Caching</i>. Switching between caches results in the cache been clear e.g. from <i>Single node</i> to <i>No cache</i> and back again would clear down the single node cache.</p>
</div>
</div>
<a id="caches_list" name="caches_list" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Retrieve Cache Types</h4>
<p>Returns the list of caches.</p>
<p>Example Requests:</p>
<div class=apiClick>caches</div>
</div>
<div class="method-example">
<code class="method-declaration">
GET https://DomainName/api/v1/caches
</code>
<code class="method-response">
[
{
"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
}
]
</code>
</div>
</div>
<a id="caches_switch" name="caches_switch"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Switch Cache</h4>
<p>Switches the cache to chosen one.</p>
</div>
<div class="method-example">
<code class="method-declaration">
PUT https://DomainName/api/v1/caches
</code>
<code class="method-request">
PUT caches
Content-Type: application/json
Request Body:
{
"cacheType": 2
}
</code>
<code class="method-response">
{
"changes": {
"cacheType": 2
}
}
</code>
</div>
</div>
<a id="configs_codes" name="configs_codes" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
@@ -19359,6 +19445,14 @@ GET https://DomainName/api/v1/search?query=000000111
</table>
</td>
</tr>
<tr class="alt">
<td><a href="#cache">Cache</a></td>
<td>caches</td>
<td></td>
<td><a href="#caches_list">List Cache Types</a></td>
<td><a href="#caches_switch">Switch Cache</a></td>
<td></td>
</tr>
<tr class="alt">
<td><a href="#centers">Center</a></td>
<td>centers</td>
@@ -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);
@@ -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");
}
}
@@ -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;
}
}
@@ -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);
@@ -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<String> REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(cacheTypeParameter));
}
@@ -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;
}
}
@@ -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;
@@ -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<String> 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;
}
}
@@ -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<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id"));
private final String resourceNameForPermissions = "CACHE";
private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<CacheData> 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<CacheData> 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<CacheData> 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);
}
}
@@ -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<Map<String, Object>>() {}.getType();
command.checkForUnsupportedParameters(typeOfMap, json, CacheApiConstants.REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<String, Object> changes = this.cacheService.switchToCache(cacheType);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).with(changes).build();
}
}
@@ -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;
}
}
@@ -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<Integer, CacheType> intToEnumMap = new HashMap<Integer, CacheType>();
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);
}
}
@@ -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<Long> {
@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();
}
}
@@ -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<PlatformCache, Long>, JpaSpecificationExecutor<PlatformCache> {
//
}
@@ -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<String, Object> switchToCache(CacheType cacheType);
}
@@ -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<String, Object> switchToCache(final CacheType toCacheType) {
final boolean ehCacheEnabled = this.configurationDomainService.isEhcacheEnabled();
final Map<String, Object> changes = this.cacheService.switchToCache(ehCacheEnabled, toCacheType);
if (!changes.isEmpty()) {
this.configurationDomainService.updateCache(toCacheType);
}
return changes;
}
}
@@ -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<String> getCacheNames() {
return this.currentCacheManager.getCacheNames();
}
public Collection<CacheData> 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<CacheData> caches = Arrays.asList(noCache, singleNodeCache, distributedCache);
return caches;
}
public Map<String, Object> switchToCache(final boolean ehcacheEnabled, final CacheType toCacheType) {
final Map<String, Object> changes = new HashMap<String, Object>();
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();
}
}
@@ -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);
}
@@ -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);
}
}
@@ -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<String> requestDataParameters) {
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, requestDataParameters);
}
}