MIFOSX-237: use of jdbcTemplate and SqlRowSet along with refactoring as a result of these changes.

This commit is contained in:
Keith Woodlock
2013-03-11 13:54:57 +00:00
parent d310d4df3c
commit be011529de
16 changed files with 1159 additions and 1220 deletions
@@ -16,209 +16,220 @@ import javax.xml.bind.annotation.XmlElementWrapper;
*/ */
public class ApiGlobalErrorResponse { public class ApiGlobalErrorResponse {
/** /**
* A developer friendly plain English description of why the HTTP error response was returned from the API. * A developer friendly plain English description of why the HTTP error
*/ * response was returned from the API.
private String developerMessage; */
private String developerMessage;
/**
* A link to a URL which provides more information on the details of this type of error.
*/
private String developerDocLink;
/**
* The HTTP status code of the response.
*/
private String httpStatusCode;
/**
* A user friendly plain English description of why the HTTP error response was returned from the API that can be presented to end users.
*/
private String defaultUserMessage;
/**
* A code that can be used for globalisation support by client applications of the API.
*/
private String userMessageGlobalisationCode;
/**
* A list of zero or more of the actual reasons for the HTTP error should they be needed. Typically used to express data validation errors with parameters passed to API.
*/
private List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
public static ApiGlobalErrorResponse unAuthenticated() { /**
* A link to a URL which provides more information on the details of this
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); * type of error.
globalErrorResponse.setHttpStatusCode("401"); */
globalErrorResponse.setDeveloperMessage("Invalid authentication details were passed in api request."); private String developerDocLink;
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authenticated");
globalErrorResponse.setDefaultUserMessage("Unauthenticated. Please login.");
return globalErrorResponse;
}
public static ApiGlobalErrorResponse invalidTenantIdentifier() {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("401");
globalErrorResponse.setDeveloperMessage("Invalid tenant details were passed in api request.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.invalid.tenant.identifier");
globalErrorResponse.setDefaultUserMessage("Invalide tenant identifier provided with request.");
return globalErrorResponse;
}
public static ApiGlobalErrorResponse unAuthorized() { /**
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); * The HTTP status code of the response.
globalErrorResponse.setHttpStatusCode("403"); */
globalErrorResponse.setDeveloperMessage("The user associated with credentials passed on this request does not have sufficient privileges to perform this action."); private String httpStatusCode;
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authorized");
globalErrorResponse.setDefaultUserMessage("Insufficient privileges to perform this action.");
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
errors.add(ApiParameterError.generalError("error.msg.not.authorized", "Insufficient privileges to perform this action."));
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse domainRuleViolation(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("403");
globalErrorResponse.setDeveloperMessage("Request was understood but caused a domain rule violation.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("validation.msg.domain.rule.violation");
globalErrorResponse.setDefaultUserMessage("Errors contain reason for domain rule violation.");
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse notFound(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) { /**
* A user friendly plain English description of why the HTTP error response
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); * was returned from the API that can be presented to end users.
globalErrorResponse.setHttpStatusCode("404"); */
globalErrorResponse.setDeveloperMessage("The requested resource is not available."); private String defaultUserMessage;
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.resource.not.found");
globalErrorResponse.setDefaultUserMessage("The requested resource is not available.");
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
errors.add(ApiParameterError.resourceIdentifierNotFound(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse dataIntegrityError(final String globalisationMessageCode, final String defaultUserMessage, final String parameterName, final Object... defaultUserMessageArgs) {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("403");
globalErrorResponse.setDeveloperMessage("The request caused a data integrity issue to be fired by the database.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
errors.add(ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, parameterName, defaultUserMessageArgs));
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage,
List<ApiParameterError> errors) {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("400");
globalErrorResponse.setDeveloperMessage("The request was invalid. This typically will happen due to validation errors which are provided.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse serverSideError(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("500");
globalErrorResponse.setDeveloperMessage("An unexpected error occured on the platform server.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.platform.server.side.error");
globalErrorResponse.setDefaultUserMessage("An unexpected error occured on the platform server.");
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
protected ApiGlobalErrorResponse() {
//
}
public ApiGlobalErrorResponse(final List<ApiParameterError> errors) { /**
this.errors = errors; * A code that can be used for globalisation support by client applications
} * of the API.
*/
private String userMessageGlobalisationCode;
@XmlElementWrapper(name="errors") /**
@XmlElement(name="errorResponse") * A list of zero or more of the actual reasons for the HTTP error should
public List<ApiParameterError> getErrors() { * they be needed. Typically used to express data validation errors with
return errors; * parameters passed to API.
} */
private List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
public void setErrors(List<ApiParameterError> errors) { public static ApiGlobalErrorResponse unAuthenticated() {
this.errors = errors;
}
public String getDeveloperMessage() { ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
return developerMessage; globalErrorResponse.setHttpStatusCode("401");
} globalErrorResponse.setDeveloperMessage("Invalid authentication details were passed in api request.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authenticated");
globalErrorResponse.setDefaultUserMessage("Unauthenticated. Please login.");
public void setDeveloperMessage(String developerMessage) { return globalErrorResponse;
this.developerMessage = developerMessage; }
}
public String getHttpStatusCode() { public static ApiGlobalErrorResponse invalidTenantIdentifier() {
return httpStatusCode;
}
public void setHttpStatusCode(String httpStatusCode) { ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
this.httpStatusCode = httpStatusCode; globalErrorResponse.setHttpStatusCode("401");
} globalErrorResponse.setDeveloperMessage("Invalid tenant details were passed in api request.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.invalid.tenant.identifier");
globalErrorResponse.setDefaultUserMessage("Invalide tenant identifier provided with request.");
public String getDefaultUserMessage() { return globalErrorResponse;
return defaultUserMessage; }
}
public void setDefaultUserMessage(String defaultUserMessage) { public static ApiGlobalErrorResponse unAuthorized(final String defaultUserMessage) {
this.defaultUserMessage = defaultUserMessage; ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
} globalErrorResponse.setHttpStatusCode("403");
globalErrorResponse
.setDeveloperMessage("The user associated with credentials passed on this request does not have sufficient privileges to perform this action.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authorized");
globalErrorResponse.setDefaultUserMessage("Insufficient privileges to perform this action.");
public String getUserMessageGlobalisationCode() { List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
return userMessageGlobalisationCode; errors.add(ApiParameterError.generalError("error.msg.not.authorized", defaultUserMessage));
} globalErrorResponse.setErrors(errors);
public void setUserMessageGlobalisationCode(String userMessageGlobalisationCode) { return globalErrorResponse;
this.userMessageGlobalisationCode = userMessageGlobalisationCode; }
}
public String getDeveloperDocLink() { public static ApiGlobalErrorResponse domainRuleViolation(final String globalisationMessageCode, final String defaultUserMessage,
return developerDocLink; final Object... defaultUserMessageArgs) {
} ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("403");
globalErrorResponse.setDeveloperMessage("Request was understood but caused a domain rule violation.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("validation.msg.domain.rule.violation");
globalErrorResponse.setDefaultUserMessage("Errors contain reason for domain rule violation.");
public void setDeveloperDocLink(String developerDocLink) { List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
this.developerDocLink = developerDocLink; errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
} globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse notFound(final String globalisationMessageCode, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("404");
globalErrorResponse.setDeveloperMessage("The requested resource is not available.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.resource.not.found");
globalErrorResponse.setDefaultUserMessage("The requested resource is not available.");
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
errors.add(ApiParameterError.resourceIdentifierNotFound(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse dataIntegrityError(final String globalisationMessageCode, final String defaultUserMessage,
final String parameterName, final Object... defaultUserMessageArgs) {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("403");
globalErrorResponse.setDeveloperMessage("The request caused a data integrity issue to be fired by the database.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
errors.add(ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, parameterName, defaultUserMessageArgs));
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage,
final List<ApiParameterError> errors) {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("400");
globalErrorResponse
.setDeveloperMessage("The request was invalid. This typically will happen due to validation errors which are provided.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse serverSideError(final String globalisationMessageCode, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("500");
globalErrorResponse.setDeveloperMessage("An unexpected error occured on the platform server.");
globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes");
globalErrorResponse.setUserMessageGlobalisationCode("error.msg.platform.server.side.error");
globalErrorResponse.setDefaultUserMessage("An unexpected error occured on the platform server.");
List<ApiParameterError> errors = new ArrayList<ApiParameterError>();
errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
globalErrorResponse.setErrors(errors);
return globalErrorResponse;
}
protected ApiGlobalErrorResponse() {
//
}
public ApiGlobalErrorResponse(final List<ApiParameterError> errors) {
this.errors = errors;
}
@XmlElementWrapper(name = "errors")
@XmlElement(name = "errorResponse")
public List<ApiParameterError> getErrors() {
return errors;
}
public void setErrors(final List<ApiParameterError> errors) {
this.errors = errors;
}
public String getDeveloperMessage() {
return developerMessage;
}
public void setDeveloperMessage(final String developerMessage) {
this.developerMessage = developerMessage;
}
public String getHttpStatusCode() {
return httpStatusCode;
}
public void setHttpStatusCode(final String httpStatusCode) {
this.httpStatusCode = httpStatusCode;
}
public String getDefaultUserMessage() {
return defaultUserMessage;
}
public void setDefaultUserMessage(final String defaultUserMessage) {
this.defaultUserMessage = defaultUserMessage;
}
public String getUserMessageGlobalisationCode() {
return userMessageGlobalisationCode;
}
public void setUserMessageGlobalisationCode(final String userMessageGlobalisationCode) {
this.userMessageGlobalisationCode = userMessageGlobalisationCode;
}
public String getDeveloperDocLink() {
return developerDocLink;
}
public void setDeveloperDocLink(final String developerDocLink) {
this.developerDocLink = developerDocLink;
}
} }
@@ -30,9 +30,11 @@ import org.springframework.stereotype.Component;
public class AccessDeniedExceptionMapper implements ExceptionMapper<AccessDeniedException> { public class AccessDeniedExceptionMapper implements ExceptionMapper<AccessDeniedException> {
@Override @Override
public Response toResponse(@SuppressWarnings("unused") AccessDeniedException exception) { public Response toResponse(final AccessDeniedException exception) {
// Status code 403 really reads as: // Status code 403 really reads as:
// "Authenticated - but not authorized": // "Authenticated - but not authorized":
return Response.status(Status.FORBIDDEN).entity(ApiGlobalErrorResponse.unAuthorized()).type(MediaType.APPLICATION_JSON).build(); final String defaultUserMessage = exception.getMessage();
return Response.status(Status.FORBIDDEN).entity(ApiGlobalErrorResponse.unAuthorized(defaultUserMessage))
.type(MediaType.APPLICATION_JSON).build();
} }
} }
@@ -29,9 +29,11 @@ import org.springframework.stereotype.Component;
public class NoAuthorizationExceptionMapper implements ExceptionMapper<NoAuthorizationException> { public class NoAuthorizationExceptionMapper implements ExceptionMapper<NoAuthorizationException> {
@Override @Override
public Response toResponse(@SuppressWarnings("unused") NoAuthorizationException exception) { public Response toResponse(final NoAuthorizationException exception) {
// Status code 403 really reads as: // Status code 403 really reads as:
// "Authenticated - but not authorized": // "Authenticated - but not authorized":
return Response.status(Status.FORBIDDEN).entity(ApiGlobalErrorResponse.unAuthorized()).type(MediaType.APPLICATION_JSON).build(); final String defaultUserMessage = exception.getMessage();
return Response.status(Status.FORBIDDEN).entity(ApiGlobalErrorResponse.unAuthorized(defaultUserMessage))
.type(MediaType.APPLICATION_JSON).build();
} }
} }
@@ -386,6 +386,7 @@ public class JsonParserHelper {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.integer.format", "The parameter " ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.integer.format", "The parameter "
+ parameterName + " has value: " + numericalValueFormatted + " which is invalid integer value for provided locale of [" + parameterName + " has value: " + numericalValueFormatted + " which is invalid integer value for provided locale of ["
+ clientApplicationLocale.toString() + "].", parameterName, numericalValueFormatted, clientApplicationLocale); + clientApplicationLocale.toString() + "].", parameterName, numericalValueFormatted, clientApplicationLocale);
error.setValue(numericalValueFormatted);
dataValidationErrors.add(error); dataValidationErrors.add(error);
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
@@ -408,6 +409,7 @@ public class JsonParserHelper {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.integer", "The parameter " + parameterName ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.integer", "The parameter " + parameterName
+ " has value: " + numericalValueFormatted + " which is invalid integer.", parameterName, numericalValueFormatted); + " has value: " + numericalValueFormatted + " which is invalid integer.", parameterName, numericalValueFormatted);
error.setValue(numericalValueFormatted);
dataValidationErrors.add(error); dataValidationErrors.add(error);
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
@@ -458,6 +460,7 @@ public class JsonParserHelper {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.decimal.format", "The parameter " ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.decimal.format", "The parameter "
+ parameterName + " has value: " + numericalValueFormatted + " which is invalid decimal value for provided locale of [" + parameterName + " has value: " + numericalValueFormatted + " which is invalid decimal value for provided locale of ["
+ clientApplicationLocale.toString() + "].", parameterName, numericalValueFormatted, clientApplicationLocale); + clientApplicationLocale.toString() + "].", parameterName, numericalValueFormatted, clientApplicationLocale);
error.setValue(numericalValueFormatted);
dataValidationErrors.add(error); dataValidationErrors.add(error);
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
@@ -53,6 +53,7 @@ public class ReportsApiResource {
this.genericDataService = genericDataService; this.genericDataService = genericDataService;
this.toApiJsonSerializer = toApiJsonSerializer; this.toApiJsonSerializer = toApiJsonSerializer;
} }
@GET @GET
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON, "application/x-msdownload" }) @Produces({ MediaType.APPLICATION_JSON, "application/x-msdownload" })
@@ -144,10 +145,10 @@ public class ReportsApiResource {
} else { } else {
json = this.toApiJsonSerializer.serializePretty(prettyPrint, result); json = this.toApiJsonSerializer.serializePretty(prettyPrint, result);
} }
return Response.ok().entity(json).type(MediaType.APPLICATION_JSON).build(); return Response.ok().entity(json).type(MediaType.APPLICATION_JSON).build();
} }
// CSV Export // CSV Export
Map<String, String> reportParams = getReportParams(queryParams, false); Map<String, String> reportParams = getReportParams(queryParams, false);
StreamingOutput result = this.readExtraDataAndReportingService.retrieveReportCSV(reportName, parameterTypeValue, reportParams); StreamingOutput result = this.readExtraDataAndReportingService.retrieveReportCSV(reportName, parameterTypeValue, reportParams);
@@ -160,15 +161,10 @@ public class ReportsApiResource {
// Anyone can run a 'report' that is simply getting possible parameter // Anyone can run a 'report' that is simply getting possible parameter
// (dropdown listbox) values. // (dropdown listbox) values.
if (!parameterType) { if (!parameterType) {
AppUser currentUser = context.authenticatedUser(); AppUser currentUser = context.authenticatedUser();
if (currentUser.hasNotPermissionForReport(reportName)) { if (currentUser.hasNotPermissionForReport(reportName)) { throw new NoAuthorizationException("Not authorised to run report: "
// FIXME - JW - message isnt passing back the message in the + reportName); }
// string in
// the json just a generalised not authorised message
throw new NoAuthorizationException("Not Authorised to Run Report: " + reportName);
}
} }
} }
@@ -15,7 +15,11 @@ public class DatatableData {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final String registeredTableName; private final String registeredTableName;
public DatatableData(final String applicationTableName, final String registeredTableName) { public static DatatableData create(final String applicationTableName, final String registeredTableName) {
return new DatatableData(applicationTableName, registeredTableName);
}
private DatatableData(final String applicationTableName, final String registeredTableName) {
this.applicationTableName = applicationTableName; this.applicationTableName = applicationTableName;
this.registeredTableName = registeredTableName; this.registeredTableName = registeredTableName;
} }
@@ -13,19 +13,19 @@ import java.util.List;
*/ */
public class GenericResultsetData { public class GenericResultsetData {
private final List<ResultsetColumnHeader> columnHeaders; private final List<ResultsetColumnHeaderData> columnHeaders;
private final List<ResultsetDataRow> data; private final List<ResultsetRowData> data;
public GenericResultsetData(final List<ResultsetColumnHeader> columnHeaders, final List<ResultsetDataRow> resultsetDataRows) { public GenericResultsetData(final List<ResultsetColumnHeaderData> columnHeaders, final List<ResultsetRowData> resultsetDataRows) {
this.columnHeaders = columnHeaders; this.columnHeaders = columnHeaders;
this.data = resultsetDataRows; this.data = resultsetDataRows;
} }
public List<ResultsetColumnHeader> getColumnHeaders() { public List<ResultsetColumnHeaderData> getColumnHeaders() {
return columnHeaders; return columnHeaders;
} }
public List<ResultsetDataRow> getData() { public List<ResultsetRowData> getData() {
return data; return data;
} }
} }
@@ -1,128 +0,0 @@
/**
* 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.dataqueries.data;
import java.util.ArrayList;
import java.util.List;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
public class ResultsetColumnHeader {
private String columnName;
private String columnType;
private Long columnLength;
private String columnDisplayType;
private boolean isColumnNullable;
private boolean isColumnPrimaryKey;
private List<ResultsetColumnValue> columnValues = new ArrayList<ResultsetColumnValue>();
public ResultsetColumnHeader() {
}
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getColumnType() {
return columnType;
}
public void setColumnType(String columnType) {
this.columnType = columnType;
}
public Long getColumnLength() {
return columnLength;
}
public void setColumnLength(Long columnLength) {
this.columnLength = columnLength;
}
public List<ResultsetColumnValue> getColumnValues() {
return columnValues;
}
public void setColumnValues(List<ResultsetColumnValue> columnValues) {
this.columnValues = columnValues;
}
public boolean isColumnNullable() {
return isColumnNullable;
}
public void setColumnNullable(boolean isColumnNullable) {
this.isColumnNullable = isColumnNullable;
}
public boolean isColumnPrimaryKey() {
return isColumnPrimaryKey;
}
public void setColumnPrimaryKey(boolean isColumnPrimaryKey) {
this.isColumnPrimaryKey = isColumnPrimaryKey;
}
public String getColumnDisplayType() {
return columnDisplayType;
}
public void setColumnDisplayType() {
if (this.getColumnValues().size() > 0) {
if (this.getColumnType().equalsIgnoreCase("int")) {
this.columnDisplayType = "CODELOOKUP";
return;
}
if (this.getColumnType().equalsIgnoreCase("varchar")) {
this.columnDisplayType = "CODEVALUE";
return;
}
throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type", "Invalid Lookup Type:" + this.getColumnType()
+ " - Column Name: " + this.getColumnName());
}
if (this.getColumnType().equalsIgnoreCase("varchar") || this.getColumnType().equalsIgnoreCase("char")) {
this.columnDisplayType = "STRING";
return;
}
if (this.getColumnType().equalsIgnoreCase("int") || this.getColumnType().equalsIgnoreCase("bigint")
|| this.getColumnType().equalsIgnoreCase("smallint") || this.getColumnType().equalsIgnoreCase("mediumint")
|| this.getColumnType().equalsIgnoreCase("tinyint")) {
this.columnDisplayType = "INTEGER";
return;
}
if (this.getColumnType().equalsIgnoreCase("date")) {
this.columnDisplayType = "DATE";
return;
}
if (this.getColumnType().equalsIgnoreCase("decimal")) {
this.columnDisplayType = "DECIMAL";
return;
}
if (this.getColumnType().equalsIgnoreCase("text") || this.getColumnType().equalsIgnoreCase("mediumtext")
|| this.getColumnType().equalsIgnoreCase("longtext") || this.getColumnType().equalsIgnoreCase("tinytext")) {
this.columnDisplayType = "TEXT";
return;
}
throw new PlatformDataIntegrityException("error.msg.unsupported.column.type", "Unsupported Column Type:" + this.getColumnType()
+ " - Column Name: " + this.getColumnName());
}
}
@@ -0,0 +1,218 @@
/**
* 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.dataqueries.data;
import java.util.ArrayList;
import java.util.List;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
/**
* Immutable data object representing a resultset column.
*/
public final class ResultsetColumnHeaderData {
private final String columnName;
private final String columnType;
@SuppressWarnings("unused")
private final Long columnLength;
private final String columnDisplayType;
private final boolean isColumnNullable;
@SuppressWarnings("unused")
private final boolean isColumnPrimaryKey;
private final List<ResultsetColumnValueData> columnValues;
public static ResultsetColumnHeaderData basic(final String columnName, final String columnType) {
final Long columnLength = null;
final boolean columnNullable = false;
final boolean columnIsPrimaryKey = false;
final List<ResultsetColumnValueData> columnValues = new ArrayList<ResultsetColumnValueData>();
return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues);
}
public static ResultsetColumnHeaderData detailed(final String columnName, final String columnType, final Long columnLength,
final boolean columnNullable, final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues) {
return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues);
}
private ResultsetColumnHeaderData(final String columnName, final String columnType, final Long columnLength,
final boolean columnNullable, final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues) {
this.columnName = columnName;
this.columnType = columnType;
this.columnLength = columnLength;
this.isColumnNullable = columnNullable;
this.isColumnPrimaryKey = columnIsPrimaryKey;
this.columnValues = columnValues;
String displayType = null;
if (this.columnValues.isEmpty()) {
if (isString()) {
displayType = "STRING";
} else if (isAnyInteger()) {
displayType = "INTEGER";
} else if (isDate()) {
displayType = "DATE";
} else if (isDecimal()) {
displayType = "DECIMAL";
} else if (isAnyText()) {
displayType = "TEXT";
} else {
throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type", "Invalid Lookup Type:" + this.columnType
+ " - Column Name: " + this.columnName);
}
} else {
if (isInt()) {
displayType = "CODELOOKUP";
} else if (isVarchar()) {
displayType = "CODEVALUE";
} else {
throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type", "Invalid Lookup Type:" + this.columnType
+ " - Column Name: " + this.columnName);
}
}
this.columnDisplayType = displayType;
}
private boolean isAnyText() {
return isText() || isTinyText() || isMediumText() || isLongText();
}
private boolean isText() {
return "text".equalsIgnoreCase(this.columnType);
}
private boolean isTinyText() {
return "tinytext".equalsIgnoreCase(this.columnType);
}
private boolean isMediumText() {
return "mediumtext".equalsIgnoreCase(this.columnType);
}
private boolean isLongText() {
return "longtext".equalsIgnoreCase(this.columnType);
}
private boolean isDecimal() {
return "decimal".equalsIgnoreCase(this.columnType);
}
private boolean isDate() {
return "date".equalsIgnoreCase(this.columnType);
}
private boolean isString() {
return isVarchar() || isChar();
}
private boolean isChar() {
return "char".equalsIgnoreCase(this.columnType);
}
private boolean isVarchar() {
return "varchar".equalsIgnoreCase(this.columnType);
}
private boolean isAnyInteger() {
return isInt() || isSmallInt() || isTinyInt() || isMediumInt() || isBigInt();
}
private boolean isInt() {
return "int".equalsIgnoreCase(this.columnType);
}
private boolean isSmallInt() {
return "smallint".equalsIgnoreCase(this.columnType);
}
private boolean isTinyInt() {
return "tinyint".equalsIgnoreCase(this.columnType);
}
private boolean isMediumInt() {
return "mediumint".equalsIgnoreCase(this.columnType);
}
private boolean isBigInt() {
return "bigint".equalsIgnoreCase(this.columnType);
}
public String getColumnName() {
return this.columnName;
}
public String getColumnType() {
return this.columnType;
}
public String getColumnDisplayType() {
return this.columnDisplayType;
}
public boolean isDateDisplayType() {
return "DATE".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isIntegerDisplayType() {
return "INTEGER".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isDecimalDisplayType() {
return "DECIMAL".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isCodeValueDisplayType() {
return "CODEVALUE".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isCodeLookupDisplayType() {
return "CODELOOKUP".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isMandatory() {
return !isOptional();
}
public boolean isOptional() {
return this.isColumnNullable;
}
public boolean hasColumnValues() {
return !this.columnValues.isEmpty();
}
public boolean isColumnValueAllowed(final String match) {
boolean allowed = false;
for (ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.matches(match)) {
allowed = true;
}
}
return allowed;
}
public boolean isColumnValueNotAllowed(final String match) {
return !isColumnValueAllowed(match);
}
public boolean isColumnCodeNotAllowed(final Integer match) {
return !isColumnCodeAllowed(match);
}
public boolean isColumnCodeAllowed(final Integer match) {
boolean allowed = false;
for (ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.codeMatches(match)) {
allowed = true;
}
}
return allowed;
}
}
@@ -1,33 +0,0 @@
/**
* 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.dataqueries.data;
public class ResultsetColumnValue {
private int id;
private String value;
public ResultsetColumnValue(int id, String value) {
this.id = id;
this.value = value;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@@ -0,0 +1,29 @@
/**
* 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.dataqueries.data;
/**
* Immutable data object representing a possible value for a given resultset
* column.
*/
public class ResultsetColumnValueData {
private final int id;
private final String value;
public ResultsetColumnValueData(final int id, final String value) {
this.id = id;
this.value = value;
}
public boolean matches(final String match) {
return match.equalsIgnoreCase(this.value);
}
public boolean codeMatches(final Integer match) {
return match.intValue() == this.id;
}
}
@@ -5,22 +5,21 @@
*/ */
package org.mifosplatform.infrastructure.dataqueries.data; package org.mifosplatform.infrastructure.dataqueries.data;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ResultsetDataRow { public class ResultsetRowData {
private List<String> row = new ArrayList<String>(); private final List<String> row;
public ResultsetDataRow() { public static ResultsetRowData create(final List<String> rowValues) {
return new ResultsetRowData(rowValues);
}
private ResultsetRowData(final List<String> rowValues) {
this.row = rowValues;
} }
public List<String> getRow() { public List<String> getRow() {
return row; return row;
} }
public void setRow(List<String> row) {
this.row = row;
}
} }
@@ -5,16 +5,13 @@
*/ */
package org.mifosplatform.infrastructure.dataqueries.service; package org.mifosplatform.infrastructure.dataqueries.service;
import javax.sql.rowset.CachedRowSet; import java.util.List;
import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData; import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
public interface GenericDataService { public interface GenericDataService {
CachedRowSet getCachedResultSet(String sql, String errorMsg);
void updateSQL(String sql, String sqlErrorMsg);
GenericResultsetData fillGenericResultSet(final String sql); GenericResultsetData fillGenericResultSet(final String sql);
String generateJsonFromGenericResultsetData(GenericResultsetData grs); String generateJsonFromGenericResultsetData(GenericResultsetData grs);
@@ -25,4 +22,5 @@ public interface GenericDataService {
String getDatabaseName(); String getDatabaseName();
List<ResultsetColumnHeaderData> fillResultsetColumnHeaders(String datatable);
} }
@@ -5,128 +5,70 @@
*/ */
package org.mifosplatform.infrastructure.dataqueries.service; package org.mifosplatform.infrastructure.dataqueries.service;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.sql.DataSource; import javax.sql.DataSource;
import javax.sql.rowset.CachedRowSet;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource; import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData; import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeader; import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetDataRow; import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnValueData;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetRowData;
import org.mifosplatform.infrastructure.dataqueries.exception.DataTableNotFoundException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.sun.rowset.CachedRowSetImpl;
//TODO - JW - Performance Item - most items (code values etc) can be cached but not doing that yet
@Service @Service
public class GenericDataServiceImpl implements GenericDataService { public class GenericDataServiceImpl implements GenericDataService {
private final JdbcTemplate jdbcTemplate;
private final DataSource dataSource; private final DataSource dataSource;
@Autowired @Autowired
public GenericDataServiceImpl(final TenantAwareRoutingDataSource dataSource) { public GenericDataServiceImpl(final TenantAwareRoutingDataSource dataSource) {
this.dataSource = dataSource; this.dataSource = dataSource;
} this.jdbcTemplate = new JdbcTemplate(this.dataSource);
@Override
public CachedRowSet getCachedResultSet(final String sql, final String sqlErrorMsg) {
// FIXME - KW - Need to reimplement this away from Sun library - could
// be mixture of Lists and jdbcTemplate.query
// long startTime = System.currentTimeMillis();
Connection db_connection = null;
Statement db_statement = null;
CachedRowSet crs = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
ResultSet rs = db_statement.executeQuery(sql);
crs = new CachedRowSetImpl();
crs.populate(rs);
// logger.info("RS Size: " + crs.size() +
// " getCachedResultSet sql: " + sql);
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), sqlErrorMsg);
} finally {
dbClose(db_statement, db_connection);
}
// long elapsed = System.currentTimeMillis() - startTime;
// logger.info("Elapsed Time: " + elapsed + " SQL: " + sql);
return crs;
}
@Override
public void updateSQL(final String sql, final String sqlErrorMsg) {
// long startTime = System.currentTimeMillis();
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
db_statement.executeUpdate(sql);
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), sqlErrorMsg);
} finally {
dbClose(db_statement, db_connection);
}
// long elapsed = System.currentTimeMillis() - startTime;
// logger.info("Elapsed Time: " + elapsed + " SQL: " + sql);
} }
@Override @Override
public GenericResultsetData fillGenericResultSet(final String sql) { public GenericResultsetData fillGenericResultSet(final String sql) {
String sqlErrorMsg = "Sql: " + sql; final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
CachedRowSet rs = getCachedResultSet(sql, sqlErrorMsg);
List<ResultsetColumnHeader> columnHeaders = new ArrayList<ResultsetColumnHeader>(); final List<ResultsetColumnHeaderData> columnHeaders = new ArrayList<ResultsetColumnHeaderData>();
List<ResultsetDataRow> resultsetDataRows = new ArrayList<ResultsetDataRow>(); final List<ResultsetRowData> resultsetDataRows = new ArrayList<ResultsetRowData>();
try { final SqlRowSetMetaData rsmd = rs.getMetaData();
ResultSetMetaData rsmd = rs.getMetaData(); for (int i = 0; i < rsmd.getColumnCount(); i++) {
String columnName = null;
String columnValue = null;
for (int i = 0; i < rsmd.getColumnCount(); i++) {
ResultsetColumnHeader rsch = new ResultsetColumnHeader();
rsch.setColumnName(rsmd.getColumnName(i + 1));
rsch.setColumnType(rsmd.getColumnTypeName(i + 1));
columnHeaders.add(rsch);
}
ResultsetDataRow resultsetDataRow; final String columnName = rsmd.getColumnName(i + 1);
while (rs.next()) { final String columnType = rsmd.getColumnTypeName(i + 1);
resultsetDataRow = new ResultsetDataRow();
List<String> columnValues = new ArrayList<String>();
for (int i = 0; i < rsmd.getColumnCount(); i++) {
columnName = rsmd.getColumnName(i + 1);
columnValue = rs.getString(columnName);
columnValues.add(columnValue);
}
resultsetDataRow.setRow(columnValues);
resultsetDataRows.add(resultsetDataRow);
}
return new GenericResultsetData(columnHeaders, resultsetDataRows); final ResultsetColumnHeaderData columnHeader = ResultsetColumnHeaderData.basic(columnName, columnType);
} catch (SQLException e) { columnHeaders.add(columnHeader);
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), sqlErrorMsg);
} }
while (rs.next()) {
final List<String> columnValues = new ArrayList<String>();
for (int i = 0; i < rsmd.getColumnCount(); i++) {
final String columnName = rsmd.getColumnName(i + 1);
final String columnValue = rs.getString(columnName);
columnValues.add(columnValue);
}
final ResultsetRowData resultsetDataRow = ResultsetRowData.create(columnValues);
resultsetDataRows.add(resultsetDataRow);
}
return new GenericResultsetData(columnHeaders, resultsetDataRows);
} }
@Override @Override
@@ -165,17 +107,16 @@ public class GenericDataServiceImpl implements GenericDataService {
writer.append("["); writer.append("[");
List<ResultsetColumnHeader> columnHeaders = grs.getColumnHeaders(); final List<ResultsetColumnHeaderData> columnHeaders = grs.getColumnHeaders();
// logger.info("NO. of Columns: " + columnHeaders.size());
List<ResultsetDataRow> data = grs.getData(); List<ResultsetRowData> data = grs.getData();
List<String> row; List<String> row;
Integer rSize; Integer rSize;
String doubleQuote = "\""; String doubleQuote = "\"";
String slashDoubleQuote = "\\\""; String slashDoubleQuote = "\\\"";
String currColType; String currColType;
String currVal; String currVal;
// logger.info("NO. of Rows: " + data.size());
for (int i = 0; i < data.size(); i++) { for (int i = 0; i < data.size(); i++) {
writer.append("\n{"); writer.append("\n{");
@@ -205,8 +146,8 @@ public class GenericDataServiceImpl implements GenericDataService {
writer.append("[" + localDate.getYear() + ", " + localDate.getMonthOfYear() + ", " + localDate.getDayOfMonth() writer.append("[" + localDate.getYear() + ", " + localDate.getMonthOfYear() + ", " + localDate.getDayOfMonth()
+ "]"); + "]");
} else { } else {
writer.append(doubleQuote + replace(currVal, doubleQuote,slashDoubleQuote) + doubleQuote); writer.append(doubleQuote + replace(currVal, doubleQuote, slashDoubleQuote) + doubleQuote);
} }
} }
} else { } else {
@@ -217,8 +158,7 @@ public class GenericDataServiceImpl implements GenericDataService {
if (i < (data.size() - 1)) { if (i < (data.size() - 1)) {
writer.append("},"); writer.append("},");
} } else {
else {
writer.append("}"); writer.append("}");
} }
} }
@@ -227,24 +167,6 @@ public class GenericDataServiceImpl implements GenericDataService {
return writer.toString(); return writer.toString();
} }
private void dbClose(Statement db_statement, Connection db_connection) {
// logger.debug("dbClose");
try {
if (db_statement != null) {
db_statement.close();
// parameter assignment in this case is ok.
db_statement = null;
}
if (db_connection != null) {
db_connection.close();
// parameter assignment in this case is ok.
db_connection = null;
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), "Error closing database connection");
}
}
@Override @Override
public String getDatabaseName() { public String getDatabaseName() {
@@ -254,4 +176,79 @@ public class GenericDataServiceImpl implements GenericDataService {
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), "Error Accessing Database Name"); throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), "Error Accessing Database Name");
} }
} }
@Override
public List<ResultsetColumnHeaderData> fillResultsetColumnHeaders(final String datatable) {
final SqlRowSet columnDefinitions = getDatatableMetaData(datatable);
final List<ResultsetColumnHeaderData> columnHeaders = new ArrayList<ResultsetColumnHeaderData>();
columnDefinitions.beforeFirst();
while (columnDefinitions.next()) {
final String columnName = columnDefinitions.getString("COLUMN_NAME");
final String isNullable = columnDefinitions.getString("IS_NULLABLE");
final String isPrimaryKey = columnDefinitions.getString("COLUMN_KEY");
final String columnType = columnDefinitions.getString("DATA_TYPE");
final Long columnLength = columnDefinitions.getLong("CHARACTER_MAXIMUM_LENGTH");
boolean columnNullable = "YES".equalsIgnoreCase(isNullable);
boolean columnIsPrimaryKey = "PRI".equalsIgnoreCase(isPrimaryKey);
List<ResultsetColumnValueData> columnValues = new ArrayList<ResultsetColumnValueData>();
if ("varchar".equalsIgnoreCase(columnType)) {
columnValues = retreiveColumnValues(columnName, "_cv");
} else if ("int".equalsIgnoreCase(columnType)) {
columnValues = retreiveColumnValues(columnName, "_cd");
}
final ResultsetColumnHeaderData rsch = ResultsetColumnHeaderData.detailed(columnName, columnType, columnLength, columnNullable,
columnIsPrimaryKey, columnValues);
columnHeaders.add(rsch);
}
return columnHeaders;
}
/*
* Candidate for using caching there to get allowed 'column values' from
* code/codevalue tables
*/
private List<ResultsetColumnValueData> retreiveColumnValues(final String columnName, final String code_suffix) {
final List<ResultsetColumnValueData> columnValues = new ArrayList<ResultsetColumnValueData>();
int codePosition = columnName.indexOf(code_suffix);
if (codePosition > 0) {
final String codeName = columnName.substring(0, codePosition);
final String sql = "select v.id, v.code_value from m_code m " + " join m_code_value v on v.code_id = m.id "
+ " where m.code_name = '" + codeName + "' order by v.order_position, v.id";
final SqlRowSet rsValues = this.jdbcTemplate.queryForRowSet(sql);
rsValues.beforeFirst();
while (rsValues.next()) {
final Integer id = rsValues.getInt("id");
final String codeValue = rsValues.getString("code_value");
columnValues.add(new ResultsetColumnValueData(id, codeValue));
}
}
return columnValues;
}
private SqlRowSet getDatatableMetaData(final String datatable) {
final String sql = "select COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, COLUMN_KEY"
+ " from INFORMATION_SCHEMA.COLUMNS " + " where TABLE_SCHEMA = schema() and TABLE_NAME = '" + datatable
+ "'order by ORDINAL_POSITION";
final SqlRowSet columnDefinitions = this.jdbcTemplate.queryForRowSet(sql);
if (columnDefinitions.next()) { return columnDefinitions; }
throw new DataTableNotFoundException(datatable);
}
} }
@@ -13,13 +13,11 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.sql.Date; import java.sql.Date;
import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.sql.DataSource; import javax.sql.DataSource;
import javax.sql.rowset.CachedRowSet;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.StreamingOutput;
@@ -28,8 +26,8 @@ import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityExce
import org.mifosplatform.infrastructure.core.service.FileUtils; import org.mifosplatform.infrastructure.core.service.FileUtils;
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource; import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData; import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeader; import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetDataRow; import org.mifosplatform.infrastructure.dataqueries.data.ResultsetRowData;
import org.mifosplatform.infrastructure.dataqueries.exception.ReportNotFoundException; import org.mifosplatform.infrastructure.dataqueries.exception.ReportNotFoundException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.domain.AppUser;
@@ -49,6 +47,8 @@ import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
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;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.lowagie.text.Document; import com.lowagie.text.Document;
@@ -59,467 +59,373 @@ import com.lowagie.text.pdf.PdfWriter;
@Service @Service
public class ReadReportingServiceImpl implements ReadReportingService { public class ReadReportingServiceImpl implements ReadReportingService {
private final DataSource dataSource; private final static Logger logger = LoggerFactory.getLogger(ReadReportingServiceImpl.class);
private final PlatformSecurityContext context;
private final JdbcTemplate jdbcTemplate;
private final static Logger logger = LoggerFactory private final DataSource dataSource;
.getLogger(ReadReportingServiceImpl.class); private final PlatformSecurityContext context;
private final GenericDataService genericDataService;
private Boolean noPentaho = false; private boolean noPentaho = false;
@Autowired @Autowired
public ReadReportingServiceImpl(final PlatformSecurityContext context, public ReadReportingServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource,
final TenantAwareRoutingDataSource dataSource) { final GenericDataService genericDataService) {
// kick off pentaho reports server // kick off pentaho reports server
ClassicEngineBoot.getInstance().start(); ClassicEngineBoot.getInstance().start();
noPentaho = false; noPentaho = false;
this.context = context; this.context = context;
this.dataSource = dataSource; this.dataSource = dataSource;
} this.jdbcTemplate = new JdbcTemplate(this.dataSource);
this.genericDataService = genericDataService;
@Autowired }
private GenericDataService genericDataService;
@Override
@Override public StreamingOutput retrieveReportCSV(final String name, final String type, final Map<String, String> queryParams) {
public StreamingOutput retrieveReportCSV(final String name,
final String type, final Map<String, String> queryParams) { return new StreamingOutput() {
return new StreamingOutput() { @Override
public void write(final OutputStream out) {
@Override try {
public void write(final OutputStream out) {
try { GenericResultsetData result = retrieveGenericResultset(name, type, queryParams);
StringBuffer sb = generateCsvFileBuffer(result);
GenericResultsetData result = retrieveGenericResultset(
name, type, queryParams); InputStream in = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
StringBuffer sb = generateCsvFileBuffer(result);
byte[] outputByte = new byte[4096];
InputStream in = new ByteArrayInputStream(sb.toString() Integer readLen = in.read(outputByte, 0, 4096);
.getBytes("UTF-8"));
while (readLen != -1) {
byte[] outputByte = new byte[4096]; out.write(outputByte, 0, readLen);
Integer readLen = in.read(outputByte, 0, 4096); readLen = in.read(outputByte, 0, 4096);
}
while (readLen != -1) { // in.close();
out.write(outputByte, 0, readLen); // out.flush();
readLen = in.read(outputByte, 0, 4096); // out.close();
} } catch (Exception e) {
// in.close(); throw new PlatformDataIntegrityException("error.msg.exception.error", e.getMessage());
// out.flush(); }
// out.close(); }
} catch (Exception e) { };
throw new PlatformDataIntegrityException(
"error.msg.exception.error", e.getMessage()); }
}
} private StringBuffer generateCsvFileBuffer(final GenericResultsetData result) {
}; StringBuffer writer = new StringBuffer();
} List<ResultsetColumnHeaderData> columnHeaders = result.getColumnHeaders();
logger.info("NO. of Columns: " + columnHeaders.size());
private StringBuffer generateCsvFileBuffer(final GenericResultsetData result) { Integer chSize = columnHeaders.size();
StringBuffer writer = new StringBuffer(); for (int i = 0; i < chSize; i++) {
writer.append('"' + columnHeaders.get(i).getColumnName() + '"');
List<ResultsetColumnHeader> columnHeaders = result.getColumnHeaders(); if (i < (chSize - 1)) writer.append(",");
logger.info("NO. of Columns: " + columnHeaders.size()); }
Integer chSize = columnHeaders.size(); writer.append('\n');
for (int i = 0; i < chSize; i++) {
writer.append('"' + columnHeaders.get(i).getColumnName() + '"'); List<ResultsetRowData> data = result.getData();
if (i < (chSize - 1)) List<String> row;
writer.append(","); Integer rSize;
} // String currCol;
writer.append('\n'); String currColType;
String currVal;
List<ResultsetDataRow> data = result.getData(); String doubleQuote = "\"";
List<String> row; String twoDoubleQuotes = doubleQuote + doubleQuote;
Integer rSize; logger.info("NO. of Rows: " + data.size());
// String currCol; for (int i = 0; i < data.size(); i++) {
String currColType; row = data.get(i).getRow();
String currVal; rSize = row.size();
String doubleQuote = "\""; for (int j = 0; j < rSize; j++) {
String twoDoubleQuotes = doubleQuote + doubleQuote; // currCol = columnHeaders.get(j).getColumnName();
logger.info("NO. of Rows: " + data.size()); currColType = columnHeaders.get(j).getColumnType();
for (int i = 0; i < data.size(); i++) { currVal = row.get(j);
row = data.get(i).getRow(); if (currVal != null) {
rSize = row.size(); if (currColType.equals("DECIMAL") || currColType.equals("DOUBLE") || currColType.equals("BIGINT")
for (int j = 0; j < rSize; j++) { || currColType.equals("SMALLINT") || currColType.equals("INT"))
// currCol = columnHeaders.get(j).getColumnName(); writer.append(currVal);
currColType = columnHeaders.get(j).getColumnType(); else
currVal = row.get(j); writer.append('"' + genericDataService.replace(currVal, doubleQuote, twoDoubleQuotes) + '"');
if (currVal != null) {
if (currColType.equals("DECIMAL") }
|| currColType.equals("DOUBLE") if (j < (rSize - 1)) writer.append(",");
|| currColType.equals("BIGINT") }
|| currColType.equals("SMALLINT") writer.append('\n');
|| currColType.equals("INT")) }
writer.append(currVal);
else return writer;
writer.append('"' + genericDataService.replace(currVal, }
doubleQuote, twoDoubleQuotes) + '"');
@Override
} public GenericResultsetData retrieveGenericResultset(final String name, final String type, final Map<String, String> queryParams) {
if (j < (rSize - 1))
writer.append(","); long startTime = System.currentTimeMillis();
} logger.info("STARTING REPORT: " + name + " Type: " + type);
writer.append('\n');
} String sql;
if (name.equals(".")) {
return writer; // this is to support api /reports - which isn't an important
} // call. It isn't used in the default reporting UI. But there is a
// need to provide an api that does bring back 'permitted' reports
@Override // PERMITTED REPORTS SQL
public GenericResultsetData retrieveGenericResultset(final String name, sql = "select r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,"
final String type, final Map<String, String> queryParams) { + " rp.parameter_id, rp.report_parameter_name, p.parameter_name" + " from stretchy_report r"
+ " left join stretchy_report_parameter rp on rp.report_id = r.report_id"
long startTime = System.currentTimeMillis(); + " left join stretchy_parameter p on p.parameter_id = rp.parameter_id" + " where exists" + " (select 'f'"
logger.info("STARTING REPORT: " + name + " Type: " + type); + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
+ " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id"
String sql; + " where ur.appuser_id = " + context.authenticatedUser().getId()
if (name.equals(".")) { + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', r.report_name))) "
// this is to support api /reports - which isn't an important + " order by r.report_name, rp.parameter_id";
// call. It isn't used in the default reporting UI. But there is a } else {
// need to provide an api that does bring back 'permitted' reports sql = getSQLtoRun(name, type, queryParams);
// PERMITTED REPORTS SQL }
sql = "select r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,"
+ " rp.parameter_id, rp.report_parameter_name, p.parameter_name" GenericResultsetData result = genericDataService.fillGenericResultSet(sql);
+ " from stretchy_report r"
+ " left join stretchy_report_parameter rp on rp.report_id = r.report_id" long elapsed = System.currentTimeMillis() - startTime;
+ " left join stretchy_parameter p on p.parameter_id = rp.parameter_id" logger.info("FINISHING Report/Request Name: " + name + " - " + type + " Elapsed Time: " + elapsed);
+ " where exists" return result;
+ " (select 'f'" }
+ " from m_appuser_role ur "
+ " join m_role r on r.id = ur.role_id" private String getSQLtoRun(final String name, final String type, final Map<String, String> queryParams) {
+ " left join m_role_permission rp on rp.role_id = r.id"
+ " left join m_permission p on p.id = rp.permission_id" String sql = getSql(name, type);
+ " where ur.appuser_id = "
+ context.authenticatedUser().getId() Set<String> keys = queryParams.keySet();
+ " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', r.report_name))) " for (String key : keys) {
+ " order by r.report_name, rp.parameter_id"; String pValue = queryParams.get(key);
} else { // logger.info("(" + key + " : " + pValue + ")");
sql = getSQLtoRun(name, type, queryParams); sql = genericDataService.replace(sql, key, pValue);
} }
GenericResultsetData result = genericDataService AppUser currentUser = context.authenticatedUser();
.fillGenericResultSet(sql); // Allows sql query to restrict data by office hierarchy if required
sql = genericDataService.replace(sql, "${currentUserHierarchy}", currentUser.getOffice().getHierarchy());
long elapsed = System.currentTimeMillis() - startTime; // Allows sql query to restrict data by current user Id if required
logger.info("FINISHING Report/Request Name: " + name + " - " + type // (typically used to return report lists containing only reports
+ " Elapsed Time: " + elapsed); // permitted to be run by the user
return result; sql = genericDataService.replace(sql, "${currentUserId}", currentUser.getId().toString());
}
sql = genericDataService.wrapSQL(sql);
private String getSQLtoRun(final String name, final String type,
final Map<String, String> queryParams) { return sql;
String sql = getSql(name, type); }
Set<String> keys = queryParams.keySet(); private String getSql(final String name, final String type) {
for (String key : keys) {
String pValue = queryParams.get(key); final String inputSql = "select " + type + "_sql as the_sql from stretchy_" + type + " where " + type + "_name = '" + name + "'";
// logger.info("(" + key + " : " + pValue + ")"); final String inputSqlWrapped = genericDataService.wrapSQL(inputSql);
sql = genericDataService.replace(sql, key, pValue);
} final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(inputSqlWrapped);
AppUser currentUser = context.authenticatedUser(); if (rs.next()) { return rs.getString("the_sql"); }
// Allows sql query to restrict data by office hierarchy if required throw new ReportNotFoundException(inputSql);
sql = genericDataService.replace(sql, "${currentUserHierarchy}", }
currentUser.getOffice().getHierarchy());
// Allows sql query to restrict data by current user Id if required @Override
// (typically used to return report lists containing only reports public String getReportType(final String reportName) {
// permitted to be run by the user
sql = genericDataService.replace(sql, "${currentUserId}", currentUser final String sql = "SELECT ifnull(report_type,'') as report_type FROM `stretchy_report` where report_name = '" + reportName + "'";
.getId().toString());
final String sqlWrapped = genericDataService.wrapSQL(sql);
sql = genericDataService.wrapSQL(sql);
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sqlWrapped);
return sql;
if (rs.next()) { return rs.getString("report_type"); }
} throw new ReportNotFoundException(sql);
}
private String getSql(final String name, final String type) {
@Override
String inputSql = "select " + type + "_sql as the_sql from stretchy_" public Response processPentahoRequest(final String reportName, final String outputTypeParam, final Map<String, String> queryParams) {
+ type + " where " + type + "_name = '" + name + "'";
inputSql = genericDataService.wrapSQL(inputSql); String outputType = "HTML";
if (StringUtils.isNotBlank(outputTypeParam)) outputType = outputTypeParam;
String sqlErrorMsg = "Sql: " + inputSql;
CachedRowSet rs = genericDataService.getCachedResultSet(inputSql, if (!(outputType.equalsIgnoreCase("HTML") || outputType.equalsIgnoreCase("PDF") || outputType.equalsIgnoreCase("XLS") || outputType
sqlErrorMsg); .equalsIgnoreCase("CSV")))
throw new PlatformDataIntegrityException("error.msg.invalid.outputType", "No matching Output Type: " + outputType);
try {
if (rs.next()) { if (noPentaho) { throw new PlatformDataIntegrityException("error.msg.no.pentaho", "Pentaho is not enabled",
return rs.getString("the_sql"); "Pentaho is not enabled"); }
}
throw new ReportNotFoundException(inputSql); // TODO - JW - use pentaho location finder like Pawel does in Mifos
// String reportPath =
} catch (SQLException e) { // "C:\\dev\\apache-tomcat-7.0.25\\webapps\\ROOT\\PentahoReports\\"
logger.info("msg: " + e.getMessage() + " sql: " + inputSql); // + reportName + ".prpt";
throw new PlatformDataIntegrityException("error.msg.sql.error", /*
e.getMessage(), sqlErrorMsg); * String reportPath = FileUtils.MIFOSX_BASE_DIR + File.separator +
} * ThreadLocalContextUtil.getTenant().getName() .replaceAll(" ",
* "").trim() + File.separator + "reports" + File.separator + reportName
} * + ".prpt";
*/
@Override final String reportPath = FileUtils.MIFOSX_BASE_DIR + File.separator + "reports" + File.separator + reportName + ".prpt";
public String getReportType(final String reportName) { logger.info("Report path: " + reportPath);
String sql = "SELECT ifnull(report_type,'') as report_type FROM `stretchy_report` where report_name = '"
+ reportName + "'"; // load report definition
ResourceManager manager = new ResourceManager();
sql = genericDataService.wrapSQL(sql); manager.registerDefaults();
Resource res;
String sqlErrorMsg = "Report Name: " + reportName + " Sql: " + sql;
CachedRowSet rs = genericDataService.getCachedResultSet(sql, try {
sqlErrorMsg); res = manager.createDirectly(reportPath, MasterReport.class);
MasterReport masterReport = (MasterReport) res.getResource();
try {
addParametersToReport(masterReport, queryParams);
if (rs.next()) {
return rs.getString("report_type"); ByteArrayOutputStream baos = new ByteArrayOutputStream();
}
throw new ReportNotFoundException(sql); if ("PDF".equalsIgnoreCase(outputType)) {
PdfReportUtil.createPDF(masterReport, baos);
} catch (SQLException e) { return Response.ok().entity(baos.toByteArray()).type("application/pdf").build();
throw new PlatformDataIntegrityException("error.msg.sql.error", }
e.getMessage(), sqlErrorMsg);
} if ("XLS".equalsIgnoreCase(outputType)) {
ExcelReportUtil.createXLS(masterReport, baos);
} return Response.ok().entity(baos.toByteArray()).type("application/vnd.ms-excel")
.header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".xls").build();
@Override }
public Response processPentahoRequest(final String reportName,
final String outputTypeParam, final Map<String, String> queryParams) { if ("CSV".equalsIgnoreCase(outputType)) {
CSVReportUtil.createCSV(masterReport, baos, "UTF-8");
String outputType = "HTML"; return Response.ok().entity(baos.toByteArray()).type("application/x-msdownload")
if (StringUtils.isNotBlank(outputTypeParam)) .header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".csv").build();
outputType = outputTypeParam; }
if (!(outputType.equalsIgnoreCase("HTML") if ("HTML".equalsIgnoreCase(outputType)) {
|| outputType.equalsIgnoreCase("PDF") HtmlReportUtil.createStreamHTML(masterReport, baos);
|| outputType.equalsIgnoreCase("XLS") || outputType return Response.ok().entity(baos.toByteArray()).type("text/html").build();
.equalsIgnoreCase("CSV"))) }
throw new PlatformDataIntegrityException( } catch (ResourceException e) {
"error.msg.invalid.outputType", "No matching Output Type: " throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
+ outputType); } catch (ReportProcessingException e) {
throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
if (noPentaho) } catch (IOException e) {
throw new PlatformDataIntegrityException("error.msg.no.pentaho", throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
"Pentaho is not enabled", "Pentaho is not enabled"); }
// TODO - JW - use pentaho location finder like Pawel does in Mifos throw new PlatformDataIntegrityException("error.msg.invalid.outputType", "No matching Output Type: " + outputType);
// String reportPath = }
// "C:\\dev\\apache-tomcat-7.0.25\\webapps\\ROOT\\PentahoReports\\"
// + reportName + ".prpt"; private void addParametersToReport(final MasterReport report, final Map<String, String> queryParams) {
/*String reportPath = FileUtils.MIFOSX_BASE_DIR
+ File.separator AppUser currentUser = context.authenticatedUser();
+ ThreadLocalContextUtil.getTenant().getName()
.replaceAll(" ", "").trim() + File.separator try {
+ "reports" + File.separator + reportName + ".prpt";*/
String reportPath = FileUtils.MIFOSX_BASE_DIR logger.info("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+ File.separator ReportParameterValues rptParamValues = report.getParameterValues();
+ "reports" + File.separator + reportName + ".prpt"; ReportParameterDefinition paramsDefinition = report.getParameterDefinition();
logger.info("Report path: " + reportPath);
/*
// load report definition * only allow integer, long, date and string parameter types and
ResourceManager manager = new ResourceManager(); * assume all mandatory - could go more detailed like Pawel did in
manager.registerDefaults(); * Mifos later and could match incoming and pentaho parameters
Resource res; * better... currently assuming they come in ok... and if not an
* error
try { */
res = manager.createDirectly(reportPath, MasterReport.class); for (ParameterDefinitionEntry paramDefEntry : paramsDefinition.getParameterDefinitions()) {
MasterReport masterReport = (MasterReport) res.getResource(); String paramName = paramDefEntry.getName();
if (!((paramName.equals("tenantdb")) || (paramName.equals("userhierarchy")))) {
addParametersToReport(masterReport, queryParams); logger.info("paramName:" + paramName);
String pValue = queryParams.get(paramName);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); if (StringUtils.isBlank(pValue))
throw new PlatformDataIntegrityException("error.msg.reporting.error", "Pentaho Parameter: " + paramName
if ("PDF".equalsIgnoreCase(outputType)) { + " - not Provided");
PdfReportUtil.createPDF(masterReport, baos);
return Response.ok().entity(baos.toByteArray()) Class<?> clazz = paramDefEntry.getValueType();
.type("application/pdf").build(); logger.info("addParametersToReport(" + paramName + " : " + pValue + " : " + clazz.getCanonicalName() + ")");
} if (clazz.getCanonicalName().equalsIgnoreCase("java.lang.Integer"))
rptParamValues.put(paramName, Integer.parseInt(pValue));
if ("XLS".equalsIgnoreCase(outputType)) { else if (clazz.getCanonicalName().equalsIgnoreCase("java.lang.Long"))
ExcelReportUtil.createXLS(masterReport, baos); rptParamValues.put(paramName, Long.parseLong(pValue));
return Response else if (clazz.getCanonicalName().equalsIgnoreCase("java.sql.Date"))
.ok() rptParamValues.put(paramName, Date.valueOf(pValue));
.entity(baos.toByteArray()) else
.type("application/vnd.ms-excel") rptParamValues.put(paramName, pValue);
.header("Content-Disposition", }
"attachment;filename="
+ reportName.replaceAll(" ", "") // tenant database name and current user's office hierarchy
+ ".xls").build(); // passed as parameters to allow multitenant penaho reporting
} // and
// data scoping
if ("CSV".equalsIgnoreCase(outputType)) { String tenantdb = dataSource.getConnection().getCatalog();
CSVReportUtil.createCSV(masterReport, baos, "UTF-8"); String userhierarchy = currentUser.getOffice().getHierarchy();
return Response logger.info("db name:" + tenantdb + " userhierarchy:" + userhierarchy);
.ok() rptParamValues.put("tenantdb", tenantdb);
.entity(baos.toByteArray()) rptParamValues.put("userhierarchy", userhierarchy);
.type("application/x-msdownload") }
.header("Content-Disposition",
"attachment;filename=" } catch (Exception e) {
+ reportName.replaceAll(" ", "") logger.error("error.msg.reporting.error:" + e.getMessage());
+ ".csv").build(); throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
} }
}
if ("HTML".equalsIgnoreCase(outputType)) {
HtmlReportUtil.createStreamHTML(masterReport, baos); @Override
return Response.ok().entity(baos.toByteArray()) public String retrieveReportPDF(final String reportName, final String type, final Map<String, String> queryParams) {
.type("text/html").build();
} String fileLocation = FileUtils.MIFOSX_BASE_DIR + File.separator + "";
} catch (ResourceException e) { if (!new File(fileLocation).isDirectory()) {
throw new PlatformDataIntegrityException( new File(fileLocation).mkdirs();
"error.msg.reporting.error", e.getMessage()); }
} catch (ReportProcessingException e) {
throw new PlatformDataIntegrityException( String genaratePdf = fileLocation + File.separator + reportName + ".pdf";
"error.msg.reporting.error", e.getMessage());
} catch (IOException e) { try {
throw new PlatformDataIntegrityException( GenericResultsetData result = retrieveGenericResultset(reportName, type, queryParams);
"error.msg.reporting.error", e.getMessage());
} List<ResultsetColumnHeaderData> columnHeaders = result.getColumnHeaders();
List<ResultsetRowData> data = result.getData();
throw new PlatformDataIntegrityException( List<String> row;
"error.msg.invalid.outputType", "No matching Output Type: "
+ outputType); logger.info("NO. of Columns: " + columnHeaders.size());
Integer chSize = columnHeaders.size();
}
Document document = new Document(PageSize.B0.rotate());
private void addParametersToReport(final MasterReport report,
final Map<String, String> queryParams) { PdfWriter.getInstance(document, new FileOutputStream(new File(fileLocation + reportName + ".pdf")));
document.open();
AppUser currentUser = context.authenticatedUser();
PdfPTable table = new PdfPTable(chSize);
try { table.setWidthPercentage(100);
logger.info("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); for (int i = 0; i < chSize; i++) {
ReportParameterValues rptParamValues = report.getParameterValues();
ReportParameterDefinition paramsDefinition = report table.addCell(columnHeaders.get(i).getColumnName());
.getParameterDefinition();
}
/* table.completeRow();
* only allow integer, long, date and string parameter types and
* assume all mandatory - could go more detailed like Pawel did in Integer rSize;
* Mifos later and could match incoming and pentaho parameters String currColType;
* better... currently assuming they come in ok... and if not an String currVal;
* error logger.info("NO. of Rows: " + data.size());
*/ for (int i = 0; i < data.size(); i++) {
for (ParameterDefinitionEntry paramDefEntry : paramsDefinition row = data.get(i).getRow();
.getParameterDefinitions()) { rSize = row.size();
String paramName = paramDefEntry.getName(); for (int j = 0; j < rSize; j++) {
if (!((paramName.equals("tenantdb")) || (paramName.equals("userhierarchy")))) { currColType = columnHeaders.get(j).getColumnType();
logger.info("paramName:" + paramName); currVal = row.get(j);
String pValue = queryParams.get(paramName); if (currVal != null) {
if (StringUtils.isBlank(pValue)) if (currColType.equals("DECIMAL") || currColType.equals("DOUBLE") || currColType.equals("BIGINT")
throw new PlatformDataIntegrityException( || currColType.equals("SMALLINT") || currColType.equals("INT")) {
"error.msg.reporting.error",
"Pentaho Parameter: " + paramName table.addCell(currVal.toString());
+ " - not Provided"); } else {
table.addCell(currVal.toString());
Class<?> clazz = paramDefEntry.getValueType(); }
logger.info("addParametersToReport(" + paramName + " : " }
+ pValue + " : " + clazz.getCanonicalName() + ")"); }
if (clazz.getCanonicalName().equalsIgnoreCase( }
"java.lang.Integer")) table.completeRow();
rptParamValues.put(paramName, Integer.parseInt(pValue)); document.add(table);
else if (clazz.getCanonicalName().equalsIgnoreCase( document.close();
"java.lang.Long")) return genaratePdf;
rptParamValues.put(paramName, Long.parseLong(pValue)); } catch (Exception e) {
else if (clazz.getCanonicalName().equalsIgnoreCase( logger.error("error.msg.reporting.error:" + e.getMessage());
"java.sql.Date")) throw new PlatformDataIntegrityException("error.msg.exception.error", e.getMessage());
rptParamValues.put(paramName, Date.valueOf(pValue)); }
else }
rptParamValues.put(paramName, pValue);
}
// tenant database name and current user's office hierarchy
// passed as parameters to allow multitenant penaho reporting and
// data scoping
String tenantdb = dataSource.getConnection().getCatalog();
String userhierarchy = currentUser.getOffice().getHierarchy();
logger.info("db name:" + tenantdb + " userhierarchy:" + userhierarchy);
rptParamValues.put("tenantdb", tenantdb);
rptParamValues.put("userhierarchy", userhierarchy);
}
} catch (Exception e) {
logger.info("error.msg.reporting.error:" + e.getMessage());
throw new PlatformDataIntegrityException(
"error.msg.reporting.error", e.getMessage());
}
}
@Override
public String retrieveReportPDF(final String reportName, final String type,
final Map<String, String> queryParams) {
String fileLocation = FileUtils.MIFOSX_BASE_DIR + File.separator + "";
if (!new File(fileLocation).isDirectory()) {
new File(fileLocation).mkdirs();
}
String genaratePdf = fileLocation + File.separator + reportName
+ ".pdf";
try {
GenericResultsetData result = retrieveGenericResultset(reportName,
type, queryParams);
List<ResultsetColumnHeader> columnHeaders = result
.getColumnHeaders();
List<ResultsetDataRow> data = result.getData();
List<String> row;
logger.info("NO. of Columns: " + columnHeaders.size());
Integer chSize = columnHeaders.size();
Document document = new Document(PageSize.B0.rotate());
PdfWriter.getInstance(document, new FileOutputStream(new File(
fileLocation + reportName + ".pdf")));
document.open();
PdfPTable table = new PdfPTable(chSize);
table.setWidthPercentage(100);
for (int i = 0; i < chSize; i++) {
table.addCell(columnHeaders.get(i).getColumnName());
}
table.completeRow();
Integer rSize;
String currColType;
String currVal;
logger.info("NO. of Rows: " + data.size());
for (int i = 0; i < data.size(); i++) {
row = data.get(i).getRow();
rSize = row.size();
for (int j = 0; j < rSize; j++) {
currColType = columnHeaders.get(j).getColumnType();
currVal = row.get(j);
if (currVal != null) {
if (currColType.equals("DECIMAL")
|| currColType.equals("DOUBLE")
|| currColType.equals("BIGINT")
|| currColType.equals("SMALLINT")
|| currColType.equals("INT")) {
table.addCell(currVal.toString());
} else {
table.addCell(currVal.toString());
}
}
}
}
table.completeRow();
document.add(table);
document.close();
return genaratePdf;
} catch (Exception e) {
throw new PlatformDataIntegrityException(
"error.msg.exception.error", e.getMessage());
}
}
} }
@@ -7,8 +7,6 @@ package org.mifosplatform.infrastructure.dataqueries.service;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -16,7 +14,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.sql.rowset.CachedRowSet; import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
@@ -26,17 +24,20 @@ import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidation
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.core.serialization.JsonParserHelper; import org.mifosplatform.infrastructure.core.serialization.JsonParserHelper;
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
import org.mifosplatform.infrastructure.dataqueries.data.DatatableData; import org.mifosplatform.infrastructure.dataqueries.data.DatatableData;
import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData; import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeader; import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnValue; import org.mifosplatform.infrastructure.dataqueries.data.ResultsetRowData;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetDataRow;
import org.mifosplatform.infrastructure.dataqueries.exception.DataTableNotFoundException; import org.mifosplatform.infrastructure.dataqueries.exception.DataTableNotFoundException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.domain.AppUser;
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;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
@@ -46,18 +47,24 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
private final static Logger logger = LoggerFactory.getLogger(ReadWriteNonCoreDataServiceImpl.class); private final static Logger logger = LoggerFactory.getLogger(ReadWriteNonCoreDataServiceImpl.class);
private final JdbcTemplate jdbcTemplate;
private final DataSource dataSource;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final FromJsonHelper fromJsonHelper; private final FromJsonHelper fromJsonHelper;
private final JsonParserHelper helper;
private final GenericDataService genericDataService;
@Autowired @Autowired
public ReadWriteNonCoreDataServiceImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper) { public ReadWriteNonCoreDataServiceImpl(final TenantAwareRoutingDataSource dataSource, final PlatformSecurityContext context,
final FromJsonHelper fromJsonHelper, final GenericDataService genericDataService) {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(this.dataSource);
this.context = context; this.context = context;
this.fromJsonHelper = fromJsonHelper; this.fromJsonHelper = fromJsonHelper;
this.helper = new JsonParserHelper();
this.genericDataService = genericDataService;
} }
@Autowired
private GenericDataService genericDataService;
@Override @Override
public List<DatatableData> retrieveDatatableNames(final String appTable) { public List<DatatableData> retrieveDatatableNames(final String appTable) {
@@ -67,25 +74,23 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} else { } else {
andClause = " and application_table_name = '" + appTable + "'"; andClause = " and application_table_name = '" + appTable + "'";
} }
// PERMITTED datatables // PERMITTED datatables
String sql = "select application_table_name, registered_table_name" + " from x_registered_table " + " where exists" final String sql = "select application_table_name, registered_table_name" + " from x_registered_table " + " where exists"
+ " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id" + " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
+ " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id" + " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id"
+ " where ur.appuser_id = " + context.authenticatedUser().getId() + " where ur.appuser_id = " + context.authenticatedUser().getId()
+ " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) " + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) "
+ andClause + " order by application_table_name, registered_table_name"; + andClause + " order by application_table_name, registered_table_name";
String sqlErrorMsg = "Application Table Name: " + appTable + " sql: " + sql; final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
CachedRowSet rs = genericDataService.getCachedResultSet(sql, sqlErrorMsg);
List<DatatableData> datatables = new ArrayList<DatatableData>(); final List<DatatableData> datatables = new ArrayList<DatatableData>();
try { while (rs.next()) {
while (rs.next()) { final String appTableName = rs.getString("application_table_name");
datatables.add(new DatatableData(rs.getString("application_table_name"), rs.getString("registered_table_name"))); final String registeredDatatableName = rs.getString("registered_table_name");
}
} catch (SQLException e) { datatables.add(DatatableData.create(appTableName, registeredDatatableName));
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), sqlErrorMsg);
} }
return datatables; return datatables;
@@ -96,63 +101,89 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
validateAppTable(appTable); validateAppTable(appTable);
String createPermission = "'CREATE_" + datatable + "'"; final String createPermission = "'CREATE_" + datatable + "'";
String createPermissionChecker = "'CREATE_" + datatable + "_CHECKER'"; final String createPermissionChecker = "'CREATE_" + datatable + "_CHECKER'";
String readPermission = "'READ_" + datatable + "'"; final String readPermission = "'READ_" + datatable + "'";
String updatePermission = "'UPDATE_" + datatable + "'"; final String updatePermission = "'UPDATE_" + datatable + "'";
String updatePermissionChecker = "'UPDATE_" + datatable + "_CHECKER'"; final String updatePermissionChecker = "'UPDATE_" + datatable + "_CHECKER'";
String deletePermission = "'DELETE_" + datatable + "'"; final String deletePermission = "'DELETE_" + datatable + "'";
String deletePermissionChecker = "'DELETE_" + datatable + "_CHECKER'"; final String deletePermissionChecker = "'DELETE_" + datatable + "_CHECKER'";
// TODO - JW - put in batch command later
String sql = "insert into x_registered_table (registered_table_name, application_table_name) values ('" + datatable + "', '"
+ appTable + "')";
genericDataService.updateSQL(sql, "SQL: " + sql); // TODO - JW - put in batch command later
/* final String registerDatatableSql = "insert into x_registered_table (registered_table_name, application_table_name) values ('"
* add all permissions, including checker permissions for maintenance + datatable + "', '" + appTable + "')";
* tasks
*/ this.jdbcTemplate.update(registerDatatableSql);
sql = "insert into m_permission (grouping, code, action_name, entity_name, can_maker_checker) values " + "('datatable', "
+ createPermission + ", 'CREATE', '" + datatable + "', true)," + "('datatable', " + createPermissionChecker final String permissionsSql = "insert into m_permission (grouping, code, action_name, entity_name, can_maker_checker) values "
+ ", 'CREATE', '" + datatable + "', false)," + "('datatable', " + readPermission + ", 'READ', '" + datatable + "', false)," + "('datatable', "
+ "('datatable', " + updatePermission + ", 'UPDATE', '" + datatable + "', true)," + "('datatable', " + createPermission
+ updatePermissionChecker + ", 'UPDATE', '" + datatable + "', false)," + "('datatable', " + deletePermission + ", 'CREATE', '"
+ ", 'DELETE', '" + datatable + "', true)," + "('datatable', " + deletePermissionChecker + ", 'DELETE', '" + datatable + datatable
+ "', true),"
+ "('datatable', "
+ createPermissionChecker
+ ", 'CREATE', '"
+ datatable
+ "', false),"
+ "('datatable', "
+ readPermission
+ ", 'READ', '"
+ datatable
+ "', false),"
+ "('datatable', "
+ updatePermission
+ ", 'UPDATE', '"
+ datatable
+ "', true),"
+ "('datatable', "
+ updatePermissionChecker
+ ", 'UPDATE', '"
+ datatable
+ "', false),"
+ "('datatable', "
+ deletePermission
+ ", 'DELETE', '"
+ datatable
+ "', true),"
+ "('datatable', "
+ deletePermissionChecker
+ ", 'DELETE', '"
+ datatable
+ "', false)"; + "', false)";
genericDataService.updateSQL(sql, "SQL: " + sql); this.jdbcTemplate.update(permissionsSql);
} }
@Override @Override
public void deregisterDatatable(final String datatable) { public void deregisterDatatable(final String datatable) {
// TODO - JW - put in batch command later // TODO - JW - put in batch command later
final String permissionList = "('CREATE_" + datatable + "', 'CREATE_" + datatable + "_CHECKER', 'READ_" + datatable + "', 'UPDATE_"
String permissionList = "('CREATE_" + datatable + "', 'CREATE_" + datatable + "_CHECKER', 'READ_" + datatable + "', 'UPDATE_"
+ datatable + "', 'UPDATE_" + datatable + "_CHECKER', 'DELETE_" + datatable + "', 'DELETE_" + datatable + "_CHECKER')"; + datatable + "', 'UPDATE_" + datatable + "_CHECKER', 'DELETE_" + datatable + "', 'DELETE_" + datatable + "_CHECKER')";
String sql = "delete from m_role_permission where m_role_permission.permission_id in (select id from m_permission where code in " String sql = "delete from m_role_permission where m_role_permission.permission_id in (select id from m_permission where code in "
+ permissionList + ")"; + permissionList + ")";
genericDataService.updateSQL(sql, "SQL: " + sql); this.jdbcTemplate.update(sql);
sql = "delete from m_permission where code in " + permissionList; sql = "delete from m_permission where code in " + permissionList;
genericDataService.updateSQL(sql, "SQL: " + sql); this.jdbcTemplate.update(sql);
sql = "delete from x_registered_table where registered_table_name = '" + datatable + "'"; sql = "delete from x_registered_table where registered_table_name = '" + datatable + "'";
genericDataService.updateSQL(sql, "SQL: " + sql); this.jdbcTemplate.update(sql);
} }
@Override @Override
public void newDatatableEntry(final String datatable, final Long appTableId, final JsonCommand command) { public void newDatatableEntry(final String datatable, final Long appTableId, final JsonCommand command) {
String appTable = getWithinScopeApplicationTableName(datatable, appTableId); String appTable = getWithinScopeApplicationTableName(datatable, appTableId);
List<ResultsetColumnHeader> columnHeaders = getDatatableResultsetColumnHeaders(datatable); final List<ResultsetColumnHeaderData> columnHeaders = this.genericDataService.fillResultsetColumnHeaders(datatable);
final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType(); final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType();
Map<String, String> dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json()); final Map<String, String> dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json());
String sql = getAddSql(columnHeaders, datatable, getFKField(appTable), appTableId, dataParams); final String sql = getAddSql(columnHeaders, datatable, getFKField(appTable), appTableId, dataParams);
genericDataService.updateSQL(sql, "SQL: " + sql); this.jdbcTemplate.update(sql);
} }
@Override @Override
@@ -166,7 +197,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
throw new PlatformDataIntegrityException("error.msg.attempting.multiple.update", "Application Table: " + datatable throw new PlatformDataIntegrityException("error.msg.attempting.multiple.update", "Application Table: " + datatable
+ " Foreign Key Id: " + appTableId); + " Foreign Key Id: " + appTableId);
String fkName = getFKField(getApplicationTableName(datatable)); String fkName = getFKField(queryForApplicationTableName(datatable));
final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType(); final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType();
Map<String, String> dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json()); Map<String, String> dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json());
@@ -175,10 +206,10 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
if (changes.size() == 0) return changes; if (changes.size() == 0) return changes;
String sql = getUpdateSql(datatable, fkName, appTableId, changes); final String sql = getUpdateSql(datatable, fkName, appTableId, changes);
if (sql != null) { if (StringUtils.isNotBlank(sql)) {
genericDataService.updateSQL(sql, "SQL: " + sql); this.jdbcTemplate.update(sql);
} else { } else {
logger.info("No Changes"); logger.info("No Changes");
} }
@@ -190,21 +221,21 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
public Map<String, Object> updateDatatableEntryOneToMany(final String datatable, final Long appTableId, final Long datatableId, public Map<String, Object> updateDatatableEntryOneToMany(final String datatable, final Long appTableId, final Long datatableId,
final JsonCommand command) { final JsonCommand command) {
GenericResultsetData grs = retrieveDataTableGenericResultSet(datatable, appTableId, null, datatableId); final GenericResultsetData grs = retrieveDataTableGenericResultSet(datatable, appTableId, null, datatableId);
if (grs.getData().size() == 0) throw new DataTableNotFoundException(datatable, appTableId); if (grs.getData().size() == 0) throw new DataTableNotFoundException(datatable, appTableId);
final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType(); final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType();
Map<String, String> dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json()); final Map<String, String> dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json());
final Map<String, Object> changes = getAffectedAndChangedColumns(grs, dataParams, "id"); final Map<String, Object> changes = getAffectedAndChangedColumns(grs, dataParams, "id");
if (changes.size() == 0) return changes; if (changes.size() == 0) return changes;
String sql = getUpdateSql(datatable, "id", datatableId, changes); final String sql = getUpdateSql(datatable, "id", datatableId, changes);
if (sql != null) { if (StringUtils.isNotBlank(sql)) {
genericDataService.updateSQL(sql, "SQL: " + sql); this.jdbcTemplate.update(sql);
} else { } else {
logger.info("No Changes"); logger.info("No Changes");
} }
@@ -215,11 +246,11 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
@Override @Override
public void deleteDatatableEntries(final String datatable, final Long appTableId) { public void deleteDatatableEntries(final String datatable, final Long appTableId) {
String appTable = getWithinScopeApplicationTableName(datatable, appTableId); final String appTable = getWithinScopeApplicationTableName(datatable, appTableId);
String sql = getDeleteEntriesSql(datatable, getFKField(appTable), appTableId); final String sql = getDeleteEntriesSql(datatable, getFKField(appTable), appTableId);
genericDataService.updateSQL(sql, "SQL: " + sql); this.jdbcTemplate.update(sql);
} }
@Override @Override
@@ -227,18 +258,18 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
getWithinScopeApplicationTableName(datatable, appTableId); getWithinScopeApplicationTableName(datatable, appTableId);
String sql = getDeleteEntrySql(datatable, datatableId); final String sql = getDeleteEntrySql(datatable, datatableId);
genericDataService.updateSQL(sql, "SQL: " + sql); this.jdbcTemplate.update(sql);
} }
@Override @Override
public GenericResultsetData retrieveDataTableGenericResultSet(final String datatable, final Long appTableId, final String order, public GenericResultsetData retrieveDataTableGenericResultSet(final String datatable, final Long appTableId, final String order,
final Long id) { final Long id) {
String appTable = getWithinScopeApplicationTableName(datatable, appTableId); final String appTable = getWithinScopeApplicationTableName(datatable, appTableId);
List<ResultsetColumnHeader> columnHeaders = getDatatableResultsetColumnHeaders(datatable); final List<ResultsetColumnHeaderData> columnHeaders = this.genericDataService.fillResultsetColumnHeaders(datatable);
String sql = ""; String sql = "";
@@ -252,20 +283,20 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
if (order != null) sql = sql + " order by " + order; if (order != null) sql = sql + " order by " + order;
List<ResultsetDataRow> result = fillDatatableResultSetDataRows(sql); List<ResultsetRowData> result = fillDatatableResultSetDataRows(sql);
return new GenericResultsetData(columnHeaders, result); return new GenericResultsetData(columnHeaders, result);
} }
private void checkMainResourceExistsWithinScope(final String appTable, final Long appTableId) { private void checkMainResourceExistsWithinScope(final String appTable, final Long appTableId) {
String unscopedSql = "select t.id from `" + appTable + "` t ${dataScopeCriteria} where t.id = " + appTableId; final String unscopedSql = "select t.id from `" + appTable + "` t ${dataScopeCriteria} where t.id = " + appTableId;
String sql = dataScopedSQL(unscopedSql, appTable); final String sql = dataScopedSQL(unscopedSql, appTable);
CachedRowSet rs = genericDataService.getCachedResultSet(sql, "SQL : " + sql); final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
if (rs.size() == 0) throw new DataTableNotFoundException(appTable, appTableId); if (!rs.next()) { throw new DataTableNotFoundException(appTable, appTableId); }
} }
private String dataScopedSQL(final String unscopedSQL, final String appTable) { private String dataScopedSQL(final String unscopedSQL, final String appTable) {
@@ -303,74 +334,50 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
throw new PlatformDataIntegrityException("error.msg.invalid.application.table", "Invalid Application Table: " + appTable); throw new PlatformDataIntegrityException("error.msg.invalid.application.table", "Invalid Application Table: " + appTable);
} }
private List<ResultsetDataRow> fillDatatableResultSetDataRows(final String sql) { private List<ResultsetRowData> fillDatatableResultSetDataRows(final String sql) {
String sqlErrorMsg = "Sql: " + sql; final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
CachedRowSet rs = genericDataService.getCachedResultSet(sql, sqlErrorMsg);
List<ResultsetDataRow> resultsetDataRows = new ArrayList<ResultsetDataRow>(); final List<ResultsetRowData> resultsetDataRows = new ArrayList<ResultsetRowData>();
try { final SqlRowSetMetaData rsmd = rs.getMetaData();
ResultSetMetaData rsmd = rs.getMetaData(); while (rs.next()) {
int columnCount = rsmd.getColumnCount(); final List<String> columnValues = new ArrayList<String>();
for (int i = 0; i < rsmd.getColumnCount(); i++) {
ResultsetDataRow resultsetDataRow; final String columnName = rsmd.getColumnName(i + 1);
String columnName = null; final String columnValue = rs.getString(columnName);
String columnValue = null; columnValues.add(columnValue);
while (rs.next()) {
resultsetDataRow = new ResultsetDataRow();
List<String> columnValues = new ArrayList<String>();
for (int i = 0; i < columnCount; i++) {
columnName = rsmd.getColumnName(i + 1);
columnValue = rs.getString(columnName);
columnValues.add(columnValue);
}
resultsetDataRow.setRow(columnValues);
resultsetDataRows.add(resultsetDataRow);
} }
return resultsetDataRows; final ResultsetRowData resultsetDataRow = ResultsetRowData.create(columnValues);
} catch (SQLException e) { resultsetDataRows.add(resultsetDataRow);
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), sqlErrorMsg);
} }
return resultsetDataRows;
} }
private String getWithinScopeApplicationTableName(final String datatable, final Long appTableId) { private String getWithinScopeApplicationTableName(final String datatable, final Long appTableId) {
String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = '" + datatable + "'";
CachedRowSet rs = genericDataService.getCachedResultSet(sql, "SQL : " + sql); final String applicationTableName = queryForApplicationTableName(datatable);
checkMainResourceExistsWithinScope(applicationTableName, appTableId);
if (rs.size() == 0) throw new DataTableNotFoundException(datatable); return applicationTableName;
try {
rs.next();
String appTable = rs.getString("application_table_name");
checkMainResourceExistsWithinScope(appTable, appTableId);
return appTable;
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage());
}
} }
private String getApplicationTableName(final String datatable) { private String queryForApplicationTableName(final String datatable) {
// TODO - JW - only used for update... can probably remove this after as final String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = '" + datatable + "'";
// its
// a reread
String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = '" + datatable + "'";
CachedRowSet rs = genericDataService.getCachedResultSet(sql, "SQL : " + sql); final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
if (rs.size() == 0) throw new DataTableNotFoundException(datatable); String applicationTableName = null;
if (rs.next()) {
try { applicationTableName = rs.getString("application_table_name");
rs.next(); } else {
return rs.getString("application_table_name"); throw new DataTableNotFoundException(datatable);
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage());
} }
return applicationTableName;
} }
private String getFKField(final String applicationTableName) { private String getFKField(final String applicationTableName) {
@@ -378,89 +385,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
return applicationTableName.substring(2) + "_id"; return applicationTableName.substring(2) + "_id";
} }
private CachedRowSet getDatatableMetaData(final String datatable) { private String getAddSql(final List<ResultsetColumnHeaderData> columnHeaders, final String datatable, final String fkName,
String sql = "select COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, COLUMN_KEY"
+ " from INFORMATION_SCHEMA.COLUMNS " + " where TABLE_SCHEMA = schema() and TABLE_NAME = '" + datatable
+ "'order by ORDINAL_POSITION";
CachedRowSet columnDefinitions = genericDataService.getCachedResultSet(sql, "SQL: " + sql);
if (columnDefinitions.size() > 0) return columnDefinitions;
throw new DataTableNotFoundException(datatable);
}
private List<ResultsetColumnHeader> getDatatableResultsetColumnHeaders(final String datatable) {
CachedRowSet columnDefinitions = getDatatableMetaData(datatable);
List<ResultsetColumnHeader> columnHeaders = new ArrayList<ResultsetColumnHeader>();
try {
while (columnDefinitions.next()) {
ResultsetColumnHeader rsch = new ResultsetColumnHeader();
rsch.setColumnName(columnDefinitions.getString("COLUMN_NAME"));
String isNullable = columnDefinitions.getString("IS_NULLABLE");
if (isNullable.equalsIgnoreCase("YES"))
rsch.setColumnNullable(true);
else
rsch.setColumnNullable(false);
String isPrimaryKey = columnDefinitions.getString("COLUMN_KEY");
if (isPrimaryKey.equalsIgnoreCase("PRI"))
rsch.setColumnPrimaryKey(true);
else
rsch.setColumnPrimaryKey(false);
Long columnLength = columnDefinitions.getLong("CHARACTER_MAXIMUM_LENGTH");
if (columnLength > 0) rsch.setColumnLength(columnDefinitions.getLong("CHARACTER_MAXIMUM_LENGTH"));
rsch.setColumnType(columnDefinitions.getString("DATA_TYPE"));
/* look for codes */
if (rsch.getColumnType().equalsIgnoreCase("varchar")) addCodesValueIfNecessary(rsch, "_cv");
if (rsch.getColumnType().equalsIgnoreCase("int")) addCodesValueIfNecessary(rsch, "_cd");
rsch.setColumnDisplayType();
columnHeaders.add(rsch);
}
;
return columnHeaders;
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage());
}
}
private void addCodesValueIfNecessary(final ResultsetColumnHeader rsch, final String code_suffix) {
int codePosition = rsch.getColumnName().indexOf(code_suffix);
if (codePosition > 0) {
String codeName = rsch.getColumnName().substring(0, codePosition);
String sql = "select v.id, v.code_value from m_code m " + " join m_code_value v on v.code_id = m.id "
+ " where m.code_name = '" + codeName + "' order by v.order_position, v.id";
CachedRowSet rsValues = genericDataService.getCachedResultSet(sql, "SQL: " + sql);
try {
while (rsValues.next()) {
rsch.getColumnValues().add(new ResultsetColumnValue(rsValues.getInt("id"), rsValues.getString("code_value")));
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage());
}
}
}
private String getAddSql(final List<ResultsetColumnHeader> columnHeaders, final String datatable, final String fkName,
final Long appTableId, final Map<String, String> queryParams) { final Long appTableId, final Map<String, String> queryParams) {
final Map<String, String> affectedColumns = getAffectedColumns(columnHeaders, queryParams, fkName); final Map<String, String> affectedColumns = getAffectedColumns(columnHeaders, queryParams, fkName);
@@ -573,8 +498,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
throw new PlatformDataIntegrityException("error.msg.invalid.columnName", "Parameter Column Name: " + key + " not found"); throw new PlatformDataIntegrityException("error.msg.invalid.columnName", "Parameter Column Name: " + key + " not found");
} }
private Map<String, String> getAffectedColumns(final List<ResultsetColumnHeader> columnHeaders, final Map<String, String> queryParams, private Map<String, String> getAffectedColumns(final List<ResultsetColumnHeaderData> columnHeaders,
final String keyFieldName) { final Map<String, String> queryParams, final String keyFieldName) {
String dateFormat = queryParams.get("dateFormat"); String dateFormat = queryParams.get("dateFormat");
Locale clientApplicationLocale = null; Locale clientApplicationLocale = null;
@@ -600,7 +525,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
// matches incoming fields with and without underscores (spaces // matches incoming fields with and without underscores (spaces
// and underscores considered the same) // and underscores considered the same)
queryParamColumnUnderscored = genericDataService.replace(key, space, underscore); queryParamColumnUnderscored = genericDataService.replace(key, space, underscore);
for (ResultsetColumnHeader columnHeader : columnHeaders) { for (ResultsetColumnHeaderData columnHeader : columnHeaders) {
if (notFound) { if (notFound) {
columnHeaderUnderscored = genericDataService.replace(columnHeader.getColumnName(), space, underscore); columnHeaderUnderscored = genericDataService.replace(columnHeader.getColumnName(), space, underscore);
if (queryParamColumnUnderscored.equalsIgnoreCase(columnHeaderUnderscored)) { if (queryParamColumnUnderscored.equalsIgnoreCase(columnHeaderUnderscored)) {
@@ -618,74 +543,84 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
return affectedColumns; return affectedColumns;
} }
private String validateColumn(final ResultsetColumnHeader columnHeader, final String pValue, final String dateFormat, private String validateColumn(final ResultsetColumnHeaderData columnHeader, final String pValue, final String dateFormat,
final Locale clientApplicationLocale) { final Locale clientApplicationLocale) {
String paramValue = pValue; String paramValue = pValue;
if ((columnHeader.getColumnDisplayType().equals("DATE")) || (columnHeader.getColumnDisplayType().equals("INTEGER")) if (columnHeader.isDateDisplayType() || columnHeader.isIntegerDisplayType() || columnHeader.isDecimalDisplayType()) {
|| (columnHeader.getColumnDisplayType().equals("DECIMAL"))) paramValue = paramValue.trim(); paramValue = paramValue.trim();
}
if ((StringUtils.isEmpty(paramValue)) && (!(columnHeader.isColumnNullable()))) { if (StringUtils.isEmpty(paramValue) && columnHeader.isMandatory()) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
ApiParameterError error = ApiParameterError.parameterError("error.msg.column.mandatory", "Mandatory", final ApiParameterError error = ApiParameterError.parameterError("error.msg.column.mandatory", "Mandatory",
columnHeader.getColumnName()); columnHeader.getColumnName());
dataValidationErrors.add(error); dataValidationErrors.add(error);
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors); dataValidationErrors);
} }
if (!StringUtils.isEmpty(paramValue)) { if (StringUtils.isNotEmpty(paramValue)) {
if (columnHeader.getColumnValues().size() > 0) { if (columnHeader.hasColumnValues()) {
// match code value or id if (columnHeader.isCodeValueDisplayType()) {
List<ResultsetColumnValue> allowedValues = columnHeader.getColumnValues();
if (columnHeader.getColumnDisplayType().equals("CODEVALUE")) { if (columnHeader.isColumnValueNotAllowed(paramValue)) {
for (ResultsetColumnValue allowedValue : allowedValues) { final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
if (paramValue.equalsIgnoreCase(allowedValue.getValue())) return paramValue; final ApiParameterError error = ApiParameterError.parameterError("error.msg.invalid.columnValue",
"Value not found in Allowed Value list", columnHeader.getColumnName(), paramValue);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
} }
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
ApiParameterError error = ApiParameterError.parameterError("error.msg.invalid.columnValue",
"Value not found in Allowed Value list", columnHeader.getColumnName(), paramValue);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
if (columnHeader.getColumnDisplayType().equals("CODELOOKUP")) { return paramValue;
for (ResultsetColumnValue allowedValue : allowedValues) { } else if (columnHeader.isCodeLookupDisplayType()) {
if (paramValue.equals(Integer.toString(allowedValue.getId()))) return paramValue;
final Integer codeLookup = Integer.valueOf(paramValue);
if (columnHeader.isColumnCodeNotAllowed(codeLookup)) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final ApiParameterError error = ApiParameterError.parameterError("error.msg.invalid.columnValue",
"Value not found in Allowed Value list", columnHeader.getColumnName(), paramValue);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
} }
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
ApiParameterError error = ApiParameterError.parameterError("error.msg.invalid.columnValue",
"Value not found in Allowed Value list", columnHeader.getColumnName(), paramValue);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
throw new PlatformDataIntegrityException("error.msg.invalid.columnType.", "Code: " + columnHeader.getColumnName() return paramValue;
+ " - Invalid Type " + columnHeader.getColumnType() + " (neither varchar nor int)"); } else {
throw new PlatformDataIntegrityException("error.msg.invalid.columnType.", "Code: " + columnHeader.getColumnName()
+ " - Invalid Type " + columnHeader.getColumnType() + " (neither varchar nor int)");
}
} }
JsonParserHelper helper = new JsonParserHelper(); if (columnHeader.isDateDisplayType()) {
final LocalDate tmpDate = helper.convertFrom(paramValue, columnHeader.getColumnName(), dateFormat, clientApplicationLocale);
if (columnHeader.getColumnDisplayType().equals("DATE")) { if (tmpDate == null) {
LocalDate tmpDate = helper.convertFrom(paramValue, columnHeader.getColumnName(), dateFormat, clientApplicationLocale); paramValue = null;
if (tmpDate == null) return null; } else {
return tmpDate.toString(); paramValue = tmpDate.toString();
}
} }
if (columnHeader.getColumnDisplayType().equals("INTEGER")) {
if (columnHeader.isIntegerDisplayType()) {
Integer tmpInt = helper.convertToInteger(paramValue, columnHeader.getColumnName(), clientApplicationLocale); Integer tmpInt = helper.convertToInteger(paramValue, columnHeader.getColumnName(), clientApplicationLocale);
if (tmpInt == null) return null; if (tmpInt == null) {
return tmpInt.toString(); paramValue = null;
} } else {
if (columnHeader.getColumnDisplayType().equals("DECIMAL")) { paramValue = tmpInt.toString();
BigDecimal tmpDecimal = helper.convertFrom(paramValue, columnHeader.getColumnName(), clientApplicationLocale); }
if (tmpDecimal == null) return null;
return tmpDecimal.toString();
} }
if (columnHeader.isDecimalDisplayType()) {
BigDecimal tmpDecimal = helper.convertFrom(paramValue, columnHeader.getColumnName(), clientApplicationLocale);
if (tmpDecimal == null) {
paramValue = null;
} else {
paramValue = tmpDecimal.toString();
}
}
} }
return paramValue; return paramValue;