MIFOSX-259: make so loanTenantById gets cached correctly - also introduce delegatingCacheManager to allow us to introduce api and configuration to support switching cache solution runtime. At present caching is disabled through use of NoOpCacheManager.

This commit is contained in:
Keith Woodlock
2013-09-11 20:51:43 +01:00
parent 822eea631f
commit 5d94776c56
15 changed files with 360 additions and 160 deletions
@@ -0,0 +1,29 @@
package org.mifosplatform.infrastructure.cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.DefaultKeyGenerator;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class PlatformCacheConfiguration implements CachingConfigurer {
@Autowired
private RuntimeDelegatingCacheManager delegatingCacheManager;
@Bean
@Override
public CacheManager cacheManager() {
return this.delegatingCacheManager;
}
@Override
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
}
@@ -0,0 +1,50 @@
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;
}
}
@@ -5,53 +5,23 @@
*/ */
package org.mifosplatform.infrastructure.core.domain; package org.mifosplatform.infrastructure.core.domain;
import javax.persistence.Column; public class MifosPlatformTenant {
import javax.persistence.Entity;
import javax.persistence.Table;
import org.springframework.data.jpa.domain.AbstractPersistable; private final Long id;
private final String tenantIdentifier;
private final String name;
private final String schemaName;
private final String schemaServer;
private final String schemaServerPort;
private final String schemaUsername;
private final String schemaPassword;
private final String timezoneId;
private final boolean autoUpdateEnabled;
public MifosPlatformTenant(final Long id, final String tenantIdentifier, final String name, final String schemaName,
@Entity final String schemaServer, final String schemaServerPort, final String schemaUsername, final String schemaPassword,
@Table(name = "tenants") final String timezoneId, final boolean autoUpdateEnabled) {
public class MifosPlatformTenant extends AbstractPersistable<Long>{ this.id = id;
@Column(name="identifier")
private String tenantIdentifier;
@Column(name = "name")
private String name;
@Column(name = "schema_name")
private String schemaName;
@Column(name = "schema_server")
private String schemaServer;
@Column(name = "schema_server_port")
private String schemaServerPort;
@Column(name = "schema_username")
private String schemaUsername;
@Column(name = "schema_password")
private String schemaPassword;
@Column(name = "timezone_id")
private String timezoneId;
@Column(name = "auto_update")
private boolean autoUpdateEnabled;
public MifosPlatformTenant() {
}
public MifosPlatformTenant(final Long id,final String tenantIdentifier, final String name, final String schemaName, final String schemaServer,
final String schemaServerPort, final String schemaUsername, final String schemaPassword, String timezoneId,
final boolean autoUpdateEnabled) {
setId(id);
this.tenantIdentifier = tenantIdentifier; this.tenantIdentifier = tenantIdentifier;
this.name = name; this.name = name;
this.schemaName = schemaName; this.schemaName = schemaName;
@@ -61,22 +31,24 @@ public class MifosPlatformTenant extends AbstractPersistable<Long>{
this.schemaPassword = schemaPassword; this.schemaPassword = schemaPassword;
this.timezoneId = timezoneId; this.timezoneId = timezoneId;
this.autoUpdateEnabled = autoUpdateEnabled; this.autoUpdateEnabled = autoUpdateEnabled;
} }
public String databaseURL() { public String databaseURL() {
String url = new StringBuilder("jdbc:mysql://").append(schemaServer).append(':').append(schemaServerPort).append('/') final String url = new StringBuilder("jdbc:mysql://").append(this.schemaServer).append(':').append(this.schemaServerPort)
.append(schemaName).toString(); .append('/').append(this.schemaName).toString();
return url; return url;
} }
public Long getId() {
return this.id;
}
public String getTenantIdentifier() { public String getTenantIdentifier() {
return this.tenantIdentifier; return this.tenantIdentifier;
} }
public String getName() { public String getName() {
return name; return this.name;
} }
public String getSchemaName() { public String getSchemaName() {
@@ -84,15 +56,15 @@ public class MifosPlatformTenant extends AbstractPersistable<Long>{
} }
public String getSchemaUsername() { public String getSchemaUsername() {
return schemaUsername; return this.schemaUsername;
} }
public String getSchemaPassword() { public String getSchemaPassword() {
return schemaPassword; return this.schemaPassword;
} }
public String getTimezoneId() { public String getTimezoneId() {
return timezoneId; return this.timezoneId;
} }
public boolean isAutoUpdateEnabled() { public boolean isAutoUpdateEnabled() {
@@ -20,7 +20,7 @@ import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil; import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.security.data.PlatformRequestLog; import org.mifosplatform.infrastructure.security.data.PlatformRequestLog;
import org.mifosplatform.infrastructure.security.exception.InvalidTenantIdentiferException; import org.mifosplatform.infrastructure.security.exception.InvalidTenantIdentiferException;
import org.mifosplatform.infrastructure.security.service.TenantDetailsService; import org.mifosplatform.infrastructure.security.service.BasicAuthTenantDetailsService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -28,6 +28,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.stereotype.Service;
/** /**
* A customised version of spring security's {@link BasicAuthenticationFilter}. * A customised version of spring security's {@link BasicAuthenticationFilter}.
@@ -44,31 +45,34 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
* If multi-tenant and basic auth credentials are invalid, a http error response * If multi-tenant and basic auth credentials are invalid, a http error response
* is returned. * is returned.
*/ */
@Service(value = "basicAuthenticationProcessingFilter")
public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFilter { public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFilter {
private final static Logger logger = LoggerFactory.getLogger(TenantAwareBasicAuthenticationFilter.class); private final static Logger logger = LoggerFactory.getLogger(TenantAwareBasicAuthenticationFilter.class);
@Autowired private final BasicAuthTenantDetailsService basicAuthTenantDetailsService;
private TenantDetailsService tenantDetailsService; private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;
private final String tenantRequestHeader = "X-Mifos-Platform-TenantId";
private final boolean exceptionIfHeaderMissing = true;
@Autowired @Autowired
private ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;
private String tenantRequestHeader = "X-Mifos-Platform-TenantId";
private boolean exceptionIfHeaderMissing = true;
public TenantAwareBasicAuthenticationFilter(final AuthenticationManager authenticationManager, public TenantAwareBasicAuthenticationFilter(final AuthenticationManager authenticationManager,
final AuthenticationEntryPoint authenticationEntryPoint) { final AuthenticationEntryPoint authenticationEntryPoint,
final BasicAuthTenantDetailsService basicAuthTenantDetailsService,
final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer) {
super(authenticationManager, authenticationEntryPoint); super(authenticationManager, authenticationEntryPoint);
this.basicAuthTenantDetailsService = basicAuthTenantDetailsService;
this.toApiJsonSerializer = toApiJsonSerializer;
} }
@Override @Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req; final HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res; final HttpServletResponse response = (HttpServletResponse) res;
StopWatch task = new StopWatch(); final StopWatch task = new StopWatch();
task.start(); task.start();
try { try {
@@ -78,23 +82,23 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
// in different origin (domain name) // in different origin (domain name)
} else { } else {
String tenantId = request.getHeader(tenantRequestHeader); String tenantIdentifier = request.getHeader(this.tenantRequestHeader);
if (org.apache.commons.lang.StringUtils.isBlank(tenantId)) { if (org.apache.commons.lang.StringUtils.isBlank(tenantIdentifier)) {
tenantId = request.getParameter("tenantIdentifier"); tenantIdentifier = request.getParameter("tenantIdentifier");
} }
if (tenantId == null && exceptionIfHeaderMissing) { throw new InvalidTenantIdentiferException( if (tenantIdentifier == null && this.exceptionIfHeaderMissing) { throw new InvalidTenantIdentiferException(
"No tenant identifier found: Add request header of '" + tenantRequestHeader "No tenant identifier found: Add request header of '" + this.tenantRequestHeader
+ "' or add the parameter 'tenantIdentifier' to query string of request URL."); } + "' or add the parameter 'tenantIdentifier' to query string of request URL."); }
// check tenants database for tenantId // check tenants database for tenantId
final MifosPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantId); final MifosPlatformTenant tenant = this.basicAuthTenantDetailsService.loadTenantById(tenantIdentifier);
ThreadLocalContextUtil.setTenant(tenant); ThreadLocalContextUtil.setTenant(tenant);
} }
super.doFilter(req, res, chain); super.doFilter(req, res, chain);
} catch (InvalidTenantIdentiferException e) { } catch (final InvalidTenantIdentiferException e) {
// deal with exception at low level // deal with exception at low level
SecurityContextHolder.getContext().setAuthentication(null); SecurityContextHolder.getContext().setAuthentication(null);
@@ -103,7 +107,7 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
} finally { } finally {
task.stop(); task.stop();
final PlatformRequestLog log = PlatformRequestLog.from(task, request); final PlatformRequestLog log = PlatformRequestLog.from(task, request);
logger.info(toApiJsonSerializer.serialize(log)); logger.info(this.toApiJsonSerializer.serialize(log));
} }
} }
} }
@@ -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.security.service;
import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
public interface BasicAuthTenantDetailsService {
MifosPlatformTenant loadTenantById(String tenantId);
}
@@ -0,0 +1,80 @@
/**
* 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.security.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
import org.mifosplatform.infrastructure.security.exception.InvalidTenantIdentiferException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
/**
* A JDBC implementation of {@link BasicAuthTenantDetailsService} for loading a
* tenants details by a <code>tenantIdentifier</code>.
*/
@Service
public class BasicAuthTenantDetailsServiceJdbc implements BasicAuthTenantDetailsService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public BasicAuthTenantDetailsServiceJdbc(@Qualifier("tenantDataSourceJndi") final DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private static final class TenantMapper implements RowMapper<MifosPlatformTenant> {
private final StringBuilder sqlBuilder = new StringBuilder(
"id, name,identifier, schema_name as schemaName, schema_server as schemaServer, schema_server_port as schemaServerPort, auto_update as autoUpdate, ")//
.append(" schema_username as schemaUsername, schema_password as schemaPassword , timezone_id as timezoneId ")//
.append(" from tenants t");//
public String schema() {
return this.sqlBuilder.toString();
}
@Override
public MifosPlatformTenant mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final String tenantIdentifier = rs.getString("identifier");
final String name = rs.getString("name");
final String schemaName = rs.getString("schemaName");
final String schemaServer = rs.getString("schemaServer");
final String schemaServerPort = rs.getString("schemaServerPort");
final String schemaUsername = rs.getString("schemaUsername");
final String schemaPassword = rs.getString("schemaPassword");
final String timezoneId = rs.getString("timezoneId");
final boolean autoUpdateEnabled = rs.getBoolean("autoUpdate");
return new MifosPlatformTenant(id, tenantIdentifier, name, schemaName, schemaServer, schemaServerPort, schemaUsername,
schemaPassword, timezoneId, autoUpdateEnabled);
}
}
@Override
@Cacheable(value = "tenantsById")
public MifosPlatformTenant loadTenantById(final String tenantIdentifier) {
try {
final TenantMapper rm = new TenantMapper();
final String sql = "select " + rm.schema() + " where t.identifier like ?";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { tenantIdentifier });
} catch (final EmptyResultDataAccessException e) {
throw new InvalidTenantIdentiferException("The tenant identifier: " + tenantIdentifier + " is not valid.");
}
}
}
@@ -37,28 +37,28 @@ public class JdbcTenantDetailsService implements TenantDetailsService {
private static final class TenantMapper implements RowMapper<MifosPlatformTenant> { private static final class TenantMapper implements RowMapper<MifosPlatformTenant> {
private StringBuilder sqlBuilder = new StringBuilder( private final StringBuilder sqlBuilder = new StringBuilder(
"id, name,identifier, schema_name as schemaName, schema_server as schemaServer, schema_server_port as schemaServerPort, auto_update as autoUpdate, ")// "id, name,identifier, schema_name as schemaName, schema_server as schemaServer, schema_server_port as schemaServerPort, auto_update as autoUpdate, ")//
.append(" schema_username as schemaUsername, schema_password as schemaPassword , timezone_id as timezoneId ")// .append(" schema_username as schemaUsername, schema_password as schemaPassword , timezone_id as timezoneId ")//
.append(" from tenants t");// .append(" from tenants t");//
public String schema() { public String schema() {
return sqlBuilder.toString(); return this.sqlBuilder.toString();
} }
@Override @Override
public MifosPlatformTenant mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { public MifosPlatformTenant mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
Long id = rs.getLong("id"); final Long id = rs.getLong("id");
String tenantIdentifier = rs.getString("identifier"); final String tenantIdentifier = rs.getString("identifier");
String name = rs.getString("name"); final String name = rs.getString("name");
String schemaName = rs.getString("schemaName"); final String schemaName = rs.getString("schemaName");
String schemaServer = rs.getString("schemaServer"); final String schemaServer = rs.getString("schemaServer");
String schemaServerPort = rs.getString("schemaServerPort"); final String schemaServerPort = rs.getString("schemaServerPort");
String schemaUsername = rs.getString("schemaUsername"); final String schemaUsername = rs.getString("schemaUsername");
String schemaPassword = rs.getString("schemaPassword"); final String schemaPassword = rs.getString("schemaPassword");
String timezoneId = rs.getString("timezoneId"); final String timezoneId = rs.getString("timezoneId");
boolean autoUpdateEnabled = rs.getBoolean("autoUpdate"); final boolean autoUpdateEnabled = rs.getBoolean("autoUpdate");
return new MifosPlatformTenant(id, tenantIdentifier,name, schemaName, schemaServer, schemaServerPort, schemaUsername, schemaPassword, return new MifosPlatformTenant(id, tenantIdentifier,name, schemaName, schemaServer, schemaServerPort, schemaUsername, schemaPassword,
timezoneId, autoUpdateEnabled); timezoneId, autoUpdateEnabled);
@@ -66,25 +66,25 @@ public class JdbcTenantDetailsService implements TenantDetailsService {
} }
@Override @Override
@Cacheable(value = "tenantsById", key = "#tenantIdentifier") @Cacheable(value = "tenantsById")
public MifosPlatformTenant loadTenantById(final String tenantIdentifier) { public MifosPlatformTenant loadTenantById(final String tenantIdentifier) {
try { try {
TenantMapper rm = new TenantMapper(); final TenantMapper rm = new TenantMapper();
String sql = "select " + rm.schema() + " where t.identifier like ?"; final String sql = "select " + rm.schema() + " where t.identifier like ?";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { tenantIdentifier }); return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { tenantIdentifier });
} catch (EmptyResultDataAccessException e) { } catch (final EmptyResultDataAccessException e) {
throw new InvalidTenantIdentiferException("The tenant identifier: " + tenantIdentifier + " is not valid."); throw new InvalidTenantIdentiferException("The tenant identifier: " + tenantIdentifier + " is not valid.");
} }
} }
@Override @Override
public List<MifosPlatformTenant> findAllTenants() { public List<MifosPlatformTenant> findAllTenants() {
TenantMapper rm = new TenantMapper(); final TenantMapper rm = new TenantMapper();
String sql = "select " + rm.schema(); final String sql = "select " + rm.schema();
List<MifosPlatformTenant> mifosPlatformTenants = jdbcTemplate.query(sql, rm, new Object[] {}); final List<MifosPlatformTenant> mifosPlatformTenants = this.jdbcTemplate.query(sql, rm, new Object[] {});
return mifosPlatformTenants; return mifosPlatformTenants;
} }
} }
@@ -14,5 +14,4 @@ public interface TenantDetailsService {
MifosPlatformTenant loadTenantById(String tenantId); MifosPlatformTenant loadTenantById(String tenantId);
List<MifosPlatformTenant> findAllTenants(); List<MifosPlatformTenant> findAllTenants();
} }
@@ -53,7 +53,7 @@ public class OfficesApiResource {
private final OfficeReadPlatformService readPlatformService; private final OfficeReadPlatformService readPlatformService;
private final DefaultToApiJsonSerializer<OfficeData> toApiJsonSerializer; private final DefaultToApiJsonSerializer<OfficeData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired @Autowired
public OfficesApiResource(final PlatformSecurityContext context, final OfficeReadPlatformService readPlatformService, public OfficesApiResource(final PlatformSecurityContext context, final OfficeReadPlatformService readPlatformService,
@@ -71,12 +71,12 @@ public class OfficesApiResource {
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String retrieveOffices(@Context final UriInfo uriInfo) { public String retrieveOffices(@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions); this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
final Collection<OfficeData> offices = this.readPlatformService.retrieveAllOffices(); final Collection<OfficeData> offices = this.readPlatformService.retrieveAllOffices();
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, offices, RESPONSE_DATA_PARAMETERS); return this.toApiJsonSerializer.serialize(settings, offices, this.RESPONSE_DATA_PARAMETERS);
} }
@GET @GET
@@ -85,15 +85,15 @@ public class OfficesApiResource {
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String retrieveOfficeTemplate(@Context final UriInfo uriInfo) { public String retrieveOfficeTemplate(@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions); this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
OfficeData office = this.readPlatformService.retrieveNewOfficeTemplate(); OfficeData office = this.readPlatformService.retrieveNewOfficeTemplate();
final Collection<OfficeData> allowedParents = this.readPlatformService.retrieveAllOfficesForDropdown(); final Collection<OfficeData> allowedParents = this.readPlatformService.retrieveAllOfficesForDropdown();
office = OfficeData.appendedTemplate(office, allowedParents); office = OfficeData.appendedTemplate(office, allowedParents);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, office, RESPONSE_DATA_PARAMETERS); return this.toApiJsonSerializer.serialize(settings, office, this.RESPONSE_DATA_PARAMETERS);
} }
@POST @POST
@@ -117,17 +117,17 @@ public class OfficesApiResource {
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String retreiveOffice(@PathParam("officeId") final Long officeId, @Context final UriInfo uriInfo) { public String retreiveOffice(@PathParam("officeId") final Long officeId, @Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions); this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
OfficeData office = this.readPlatformService.retrieveOffice(officeId); OfficeData office = this.readPlatformService.retrieveOffice(officeId);
if (settings.isTemplate()) { if (settings.isTemplate()) {
Collection<OfficeData> allowedParents = this.readPlatformService.retrieveAllowedParents(officeId); final Collection<OfficeData> allowedParents = this.readPlatformService.retrieveAllowedParents(officeId);
office = OfficeData.appendedTemplate(office, allowedParents); office = OfficeData.appendedTemplate(office, allowedParents);
} }
return this.toApiJsonSerializer.serialize(settings, office, RESPONSE_DATA_PARAMETERS); return this.toApiJsonSerializer.serialize(settings, office, this.RESPONSE_DATA_PARAMETERS);
} }
@PUT @PUT
@@ -7,11 +7,14 @@ package org.mifosplatform.organisation.office.service;
import java.util.Collection; import java.util.Collection;
import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
import org.mifosplatform.organisation.office.data.OfficeData; import org.mifosplatform.organisation.office.data.OfficeData;
import org.mifosplatform.organisation.office.data.OfficeTransactionData; import org.mifosplatform.organisation.office.data.OfficeTransactionData;
public interface OfficeReadPlatformService { public interface OfficeReadPlatformService {
MifosPlatformTenant loadTenantById(String tenantIdentifier);
Collection<OfficeData> retrieveAllOffices(); Collection<OfficeData> retrieveAllOffices();
Collection<OfficeData> retrieveAllOfficesForDropdown(); Collection<OfficeData> retrieveAllOfficesForDropdown();
@@ -11,9 +11,13 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import javax.sql.DataSource;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.infrastructure.security.exception.InvalidTenantIdentiferException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformService; import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformService;
@@ -22,6 +26,7 @@ import org.mifosplatform.organisation.office.data.OfficeTransactionData;
import org.mifosplatform.organisation.office.exception.OfficeNotFoundException; import org.mifosplatform.organisation.office.exception.OfficeNotFoundException;
import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
@@ -32,16 +37,19 @@ import org.springframework.stereotype.Service;
public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService { public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService {
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final JdbcTemplate tenantsJdbcTemplate;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final CurrencyReadPlatformService currencyReadPlatformService; private final CurrencyReadPlatformService currencyReadPlatformService;
private final static String nameDecoratedBaseOnHierarchy = "concat(substring('........................................', 1, ((LENGTH(o.hierarchy) - LENGTH(REPLACE(o.hierarchy, '.', '')) - 1) * 4)), o.name)"; private final static String nameDecoratedBaseOnHierarchy = "concat(substring('........................................', 1, ((LENGTH(o.hierarchy) - LENGTH(REPLACE(o.hierarchy, '.', '')) - 1) * 4)), o.name)";
@Autowired @Autowired
public OfficeReadPlatformServiceImpl(final PlatformSecurityContext context, public OfficeReadPlatformServiceImpl(final PlatformSecurityContext context,
final CurrencyReadPlatformService currencyReadPlatformService, final RoutingDataSource dataSource) { final CurrencyReadPlatformService currencyReadPlatformService, final RoutingDataSource dataSource,
@Qualifier("tenantDataSourceJndi") final DataSource tenantsJndiDataSource) {
this.context = context; this.context = context;
this.currencyReadPlatformService = currencyReadPlatformService; this.currencyReadPlatformService = currencyReadPlatformService;
this.jdbcTemplate = new JdbcTemplate(dataSource); this.jdbcTemplate = new JdbcTemplate(dataSource);
this.tenantsJdbcTemplate = new JdbcTemplate(tenantsJndiDataSource);
} }
private static final class OfficeMapper implements RowMapper<OfficeData> { private static final class OfficeMapper implements RowMapper<OfficeData> {
@@ -101,25 +109,25 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
@Override @Override
public OfficeTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { public OfficeTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
Long id = rs.getLong("id"); final Long id = rs.getLong("id");
LocalDate transactionDate = JdbcSupport.getLocalDate(rs, "transactionDate"); final LocalDate transactionDate = JdbcSupport.getLocalDate(rs, "transactionDate");
Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId"); final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId");
String fromOfficeName = rs.getString("fromOfficeName"); final String fromOfficeName = rs.getString("fromOfficeName");
Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId"); final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId");
String toOfficeName = rs.getString("toOfficeName"); final String toOfficeName = rs.getString("toOfficeName");
String currencyCode = rs.getString("currencyCode"); final String currencyCode = rs.getString("currencyCode");
String currencyName = rs.getString("currencyName"); final String currencyName = rs.getString("currencyName");
String currencyNameCode = rs.getString("currencyNameCode"); final String currencyNameCode = rs.getString("currencyNameCode");
String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol,
currencyDisplaySymbol, currencyNameCode); currencyNameCode);
BigDecimal transactionAmount = rs.getBigDecimal("transactionAmount"); final BigDecimal transactionAmount = rs.getBigDecimal("transactionAmount");
String description = rs.getString("description"); final String description = rs.getString("description");
return OfficeTransactionData.instance(id, transactionDate, fromOfficeId, fromOfficeName, toOfficeId, toOfficeName, return OfficeTransactionData.instance(id, transactionDate, fromOfficeId, fromOfficeName, toOfficeId, toOfficeName,
currencyData, transactionAmount, description); currencyData, transactionAmount, description);
@@ -130,13 +138,13 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
@Cacheable(value = "offices", key = "T(org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'of')") @Cacheable(value = "offices", key = "T(org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'of')")
public Collection<OfficeData> retrieveAllOffices() { public Collection<OfficeData> retrieveAllOffices() {
AppUser currentUser = context.authenticatedUser(); final AppUser currentUser = this.context.authenticatedUser();
String hierarchy = currentUser.getOffice().getHierarchy(); final String hierarchy = currentUser.getOffice().getHierarchy();
String hierarchySearchString = hierarchy + "%"; final String hierarchySearchString = hierarchy + "%";
OfficeMapper rm = new OfficeMapper(); final OfficeMapper rm = new OfficeMapper();
String sql = "select " + rm.officeSchema() + "where o.hierarchy like ? order by o.hierarchy"; final String sql = "select " + rm.officeSchema() + "where o.hierarchy like ? order by o.hierarchy";
return this.jdbcTemplate.query(sql, rm, new Object[] { hierarchySearchString }); return this.jdbcTemplate.query(sql, rm, new Object[] { hierarchySearchString });
} }
@@ -144,7 +152,7 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
@Override @Override
@Cacheable(value = "officesForDropdown", key = "T(org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'ofd')") @Cacheable(value = "officesForDropdown", key = "T(org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'ofd')")
public Collection<OfficeData> retrieveAllOfficesForDropdown() { public Collection<OfficeData> retrieveAllOfficesForDropdown() {
final AppUser currentUser = context.authenticatedUser(); final AppUser currentUser = this.context.authenticatedUser();
final String hierarchy = currentUser.getOffice().getHierarchy(); final String hierarchy = currentUser.getOffice().getHierarchy();
final String hierarchySearchString = hierarchy + "%"; final String hierarchySearchString = hierarchy + "%";
@@ -160,15 +168,15 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
public OfficeData retrieveOffice(final Long officeId) { public OfficeData retrieveOffice(final Long officeId) {
try { try {
context.authenticatedUser(); this.context.authenticatedUser();
OfficeMapper rm = new OfficeMapper(); final OfficeMapper rm = new OfficeMapper();
String sql = "select " + rm.officeSchema() + " where o.id = ?"; final String sql = "select " + rm.officeSchema() + " where o.id = ?";
OfficeData selectedOffice = this.jdbcTemplate.queryForObject(sql, rm, new Object[] { officeId }); final OfficeData selectedOffice = this.jdbcTemplate.queryForObject(sql, rm, new Object[] { officeId });
return selectedOffice; return selectedOffice;
} catch (EmptyResultDataAccessException e) { } catch (final EmptyResultDataAccessException e) {
throw new OfficeNotFoundException(officeId); throw new OfficeNotFoundException(officeId);
} }
} }
@@ -176,7 +184,7 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
@Override @Override
public OfficeData retrieveNewOfficeTemplate() { public OfficeData retrieveNewOfficeTemplate() {
context.authenticatedUser(); this.context.authenticatedUser();
return OfficeData.template(null, new LocalDate()); return OfficeData.template(null, new LocalDate());
} }
@@ -184,13 +192,13 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
@Override @Override
public Collection<OfficeData> retrieveAllowedParents(final Long officeId) { public Collection<OfficeData> retrieveAllowedParents(final Long officeId) {
context.authenticatedUser(); this.context.authenticatedUser();
Collection<OfficeData> filterParentLookups = new ArrayList<OfficeData>(); final Collection<OfficeData> filterParentLookups = new ArrayList<OfficeData>();
if (isNotHeadOffice(officeId)) { if (isNotHeadOffice(officeId)) {
Collection<OfficeData> parentLookups = retrieveAllOfficesForDropdown(); final Collection<OfficeData> parentLookups = retrieveAllOfficesForDropdown();
for (OfficeData office : parentLookups) { for (final OfficeData office : parentLookups) {
if (!office.hasIdentifyOf(officeId)) { if (!office.hasIdentifyOf(officeId)) {
filterParentLookups.add(office); filterParentLookups.add(office);
} }
@@ -207,13 +215,13 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
@Override @Override
public Collection<OfficeTransactionData> retrieveAllOfficeTransactions() { public Collection<OfficeTransactionData> retrieveAllOfficeTransactions() {
AppUser currentUser = context.authenticatedUser(); final AppUser currentUser = this.context.authenticatedUser();
String hierarchy = currentUser.getOffice().getHierarchy(); final String hierarchy = currentUser.getOffice().getHierarchy();
String hierarchySearchString = hierarchy + "%"; final String hierarchySearchString = hierarchy + "%";
OfficeTransactionMapper rm = new OfficeTransactionMapper(); final OfficeTransactionMapper rm = new OfficeTransactionMapper();
String sql = "select " + rm.schema() final String sql = "select " + rm.schema()
+ " where (fromoff.hierarchy like ? or tooff.hierarchy like ?) order by ot.transaction_date, ot.id"; + " where (fromoff.hierarchy like ? or tooff.hierarchy like ?) order by ot.transaction_date, ot.id";
return this.jdbcTemplate.query(sql, rm, new Object[] { hierarchySearchString, hierarchySearchString }); return this.jdbcTemplate.query(sql, rm, new Object[] { hierarchySearchString, hierarchySearchString });
@@ -221,10 +229,10 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
@Override @Override
public OfficeTransactionData retrieveNewOfficeTransactionDetails() { public OfficeTransactionData retrieveNewOfficeTransactionDetails() {
context.authenticatedUser(); this.context.authenticatedUser();
final Collection<OfficeData> parentLookups = retrieveAllOfficesForDropdown(); final Collection<OfficeData> parentLookups = retrieveAllOfficesForDropdown();
final Collection<CurrencyData> currencyOptions = currencyReadPlatformService.retrieveAllowedCurrencies(); final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
return OfficeTransactionData.template(new LocalDate(), parentLookups, currencyOptions); return OfficeTransactionData.template(new LocalDate(), parentLookups, currencyOptions);
} }
@@ -232,4 +240,48 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
public PlatformSecurityContext getContext() { public PlatformSecurityContext getContext() {
return this.context; return this.context;
} }
@Override
@Cacheable(value = "tenantsById", key="#tenantIdentifier")
public MifosPlatformTenant loadTenantById(final String tenantIdentifier) {
try {
final TenantMapper rm = new TenantMapper();
final String sql = "select " + rm.schema() + " where t.identifier like ?";
return this.tenantsJdbcTemplate.queryForObject(sql, rm, new Object[] { tenantIdentifier });
} catch (final EmptyResultDataAccessException e) {
throw new InvalidTenantIdentiferException("The tenant identifier: " + tenantIdentifier + " is not valid.");
}
}
private static final class TenantMapper implements RowMapper<MifosPlatformTenant> {
private final StringBuilder sqlBuilder = new StringBuilder(
"id, name,identifier, schema_name as schemaName, schema_server as schemaServer, schema_server_port as schemaServerPort, auto_update as autoUpdate, ")//
.append(" schema_username as schemaUsername, schema_password as schemaPassword , timezone_id as timezoneId ")//
.append(" from tenants t");//
public String schema() {
return this.sqlBuilder.toString();
}
@Override
public MifosPlatformTenant mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final String tenantIdentifier = rs.getString("identifier");
final String name = rs.getString("name");
final String schemaName = rs.getString("schemaName");
final String schemaServer = rs.getString("schemaServer");
final String schemaServerPort = rs.getString("schemaServerPort");
final String schemaUsername = rs.getString("schemaUsername");
final String schemaPassword = rs.getString("schemaPassword");
final String timezoneId = rs.getString("timezoneId");
final boolean autoUpdateEnabled = rs.getBoolean("autoUpdate");
return new MifosPlatformTenant(id, tenantIdentifier, name, schemaName, schemaServer, schemaServerPort, schemaUsername,
schemaPassword, timezoneId, autoUpdateEnabled);
}
}
} }
@@ -5,7 +5,6 @@
*/ */
package org.mifosplatform.useradministration.domain; package org.mifosplatform.useradministration.domain;
import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -26,7 +25,7 @@ import org.mifosplatform.useradministration.data.RoleData;
import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity @Entity
@Table(name = "m_role", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name")}) @Table(name = "m_role", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name") })
public class Role extends AbstractPersistable<Long> { public class Role extends AbstractPersistable<Long> {
@Column(name = "name", unique = true, nullable = false, length = 100) @Column(name = "name", unique = true, nullable = false, length = 100)
@@ -100,7 +99,7 @@ public class Role extends AbstractPersistable<Long> {
public boolean hasPermissionTo(final String permissionCode) { public boolean hasPermissionTo(final String permissionCode) {
boolean match = false; boolean match = false;
for (Permission permission : this.permissions) { for (final Permission permission : this.permissions) {
if (permission.hasCode(permissionCode)) { if (permission.hasCode(permissionCode)) {
match = true; match = true;
break; break;
@@ -110,6 +109,6 @@ public class Role extends AbstractPersistable<Long> {
} }
public RoleData toData() { public RoleData toData() {
return new RoleData(this.getId(), this.name, this.description); return new RoleData(getId(), this.name, this.description);
} }
} }
@@ -24,8 +24,6 @@
<tx:annotation-driven /> <tx:annotation-driven />
<cache:annotation-driven />
<context:component-scan base-package="org.mifosplatform.accounting.*, <context:component-scan base-package="org.mifosplatform.accounting.*,
org.mifosplatform.commands.handler.*, org.mifosplatform.commands.handler.*,
org.mifosplatform.commands.service.*, org.mifosplatform.commands.service.*,
@@ -17,10 +17,10 @@
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
p:cacheManager-ref="ehcache" /> p:cacheManager-ref="ehcacheInstance" />
<bean id="ehcache" <bean id="ehcacheInstance"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:configLocation="classpath:/META-INF/spring/ehcache.xml" p:shared="true" /> p:configLocation="classpath:/META-INF/spring/ehcache.xml" p:shared="true" />
@@ -29,11 +29,12 @@
<beans:property name="realmName" value="Mifos Platform API" /> <beans:property name="realmName" value="Mifos Platform API" />
</beans:bean> </beans:bean>
<!--
<beans:bean id="basicAuthenticationProcessingFilter" class="org.mifosplatform.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter" depends-on="cacheManager"> <beans:bean id="basicAuthenticationProcessingFilter" class="org.mifosplatform.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter" depends-on="cacheManager">
<beans:constructor-arg ref="authenticationManager" /> <beans:constructor-arg ref="authenticationManager" />
<beans:constructor-arg ref="basicAuthenticationEntryPoint" /> <beans:constructor-arg ref="basicAuthenticationEntryPoint" />
</beans:bean> </beans:bean>
-->
<beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"> <beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
<beans:constructor-arg value="256"/> <beans:constructor-arg value="256"/>
</beans:bean> </beans:bean>