From be011529de55818a47c0d9933a474864644a908b Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Mon, 11 Mar 2013 13:54:57 +0000 Subject: [PATCH] MIFOSX-237: use of jdbcTemplate and SqlRowSet along with refactoring as a result of these changes. --- .../core/data/ApiGlobalErrorResponse.java | 389 ++++---- .../AccessDeniedExceptionMapper.java | 6 +- .../NoAuthorizationExceptionMapper.java | 6 +- .../core/serialization/JsonParserHelper.java | 3 + .../dataqueries/api/ReportsApiResource.java | 14 +- .../dataqueries/data/DatatableData.java | 6 +- .../data/GenericResultsetData.java | 10 +- .../data/ResultsetColumnHeader.java | 128 --- .../data/ResultsetColumnHeaderData.java | 218 +++++ .../data/ResultsetColumnValue.java | 33 - .../data/ResultsetColumnValueData.java | 29 + ...tsetDataRow.java => ResultsetRowData.java} | 15 +- .../service/GenericDataService.java | 8 +- .../service/GenericDataServiceImpl.java | 229 +++-- .../service/ReadReportingServiceImpl.java | 840 ++++++++---------- .../ReadWriteNonCoreDataServiceImpl.java | 445 ++++------ 16 files changed, 1159 insertions(+), 1220 deletions(-) delete mode 100644 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeader.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java delete mode 100644 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnValue.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnValueData.java rename mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/{ResultsetDataRow.java => ResultsetRowData.java} (57%) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/ApiGlobalErrorResponse.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/ApiGlobalErrorResponse.java index 935739b78..64e814cb5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/ApiGlobalErrorResponse.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/ApiGlobalErrorResponse.java @@ -16,209 +16,220 @@ import javax.xml.bind.annotation.XmlElementWrapper; */ public class ApiGlobalErrorResponse { - /** - * A developer friendly plain English description of why the HTTP error response was returned from the API. - */ - 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 errors = new ArrayList(); + /** + * A developer friendly plain English description of why the HTTP error + * response was returned from the API. + */ + private String developerMessage; - public static ApiGlobalErrorResponse unAuthenticated() { - - ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); - 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."); - - 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; - } + /** + * A link to a URL which provides more information on the details of this + * type of error. + */ + private String developerDocLink; - public static ApiGlobalErrorResponse unAuthorized() { - 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."); - - List errors = new ArrayList(); - 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 errors = new ArrayList(); - errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs)); - globalErrorResponse.setErrors(errors); - - return globalErrorResponse; - } - + /** + * The HTTP status code of the response. + */ + private String httpStatusCode; - public static ApiGlobalErrorResponse notFound(String globalisationMessageCode, String defaultUserMessage, 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 errors = new ArrayList(); - 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 errors = new ArrayList(); - errors.add(ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, parameterName, defaultUserMessageArgs)); - globalErrorResponse.setErrors(errors); - - return globalErrorResponse; - } - - public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage, - List 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 errors = new ArrayList(); - errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs)); - globalErrorResponse.setErrors(errors); - - return globalErrorResponse; - } - - protected ApiGlobalErrorResponse() { - // - } + /** + * 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; - public ApiGlobalErrorResponse(final List 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") - public List getErrors() { - return errors; - } + /** + * 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 errors = new ArrayList(); - public void setErrors(List errors) { - this.errors = errors; - } + public static ApiGlobalErrorResponse unAuthenticated() { - public String getDeveloperMessage() { - return developerMessage; - } + ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); + 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) { - this.developerMessage = developerMessage; - } + return globalErrorResponse; + } - public String getHttpStatusCode() { - return httpStatusCode; - } + public static ApiGlobalErrorResponse invalidTenantIdentifier() { - public void setHttpStatusCode(String httpStatusCode) { - this.httpStatusCode = httpStatusCode; - } + 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."); - public String getDefaultUserMessage() { - return defaultUserMessage; - } + return globalErrorResponse; + } - public void setDefaultUserMessage(String defaultUserMessage) { - this.defaultUserMessage = defaultUserMessage; - } + public static ApiGlobalErrorResponse unAuthorized(final String 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() { - return userMessageGlobalisationCode; - } + List errors = new ArrayList(); + errors.add(ApiParameterError.generalError("error.msg.not.authorized", defaultUserMessage)); + globalErrorResponse.setErrors(errors); - public void setUserMessageGlobalisationCode(String userMessageGlobalisationCode) { - this.userMessageGlobalisationCode = userMessageGlobalisationCode; - } + return globalErrorResponse; + } - public String getDeveloperDocLink() { - return developerDocLink; - } + public static ApiGlobalErrorResponse domainRuleViolation(final String globalisationMessageCode, final String defaultUserMessage, + 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) { - this.developerDocLink = developerDocLink; - } + List errors = new ArrayList(); + 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 errors = new ArrayList(); + 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 errors = new ArrayList(); + 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 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 errors = new ArrayList(); + errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs)); + globalErrorResponse.setErrors(errors); + + return globalErrorResponse; + } + + protected ApiGlobalErrorResponse() { + // + } + + public ApiGlobalErrorResponse(final List errors) { + this.errors = errors; + } + + @XmlElementWrapper(name = "errors") + @XmlElement(name = "errorResponse") + public List getErrors() { + return errors; + } + + public void setErrors(final List 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; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/AccessDeniedExceptionMapper.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/AccessDeniedExceptionMapper.java index 54faacbc1..fef7fefb9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/AccessDeniedExceptionMapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/AccessDeniedExceptionMapper.java @@ -30,9 +30,11 @@ import org.springframework.stereotype.Component; public class AccessDeniedExceptionMapper implements ExceptionMapper { @Override - public Response toResponse(@SuppressWarnings("unused") AccessDeniedException exception) { + public Response toResponse(final AccessDeniedException exception) { // Status code 403 really reads as: // "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(); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/NoAuthorizationExceptionMapper.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/NoAuthorizationExceptionMapper.java index db6ad137a..d537f4983 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/NoAuthorizationExceptionMapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/NoAuthorizationExceptionMapper.java @@ -29,9 +29,11 @@ import org.springframework.stereotype.Component; public class NoAuthorizationExceptionMapper implements ExceptionMapper { @Override - public Response toResponse(@SuppressWarnings("unused") NoAuthorizationException exception) { + public Response toResponse(final NoAuthorizationException exception) { // Status code 403 really reads as: // "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(); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/JsonParserHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/JsonParserHelper.java index 11057429f..bbe91d628 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/JsonParserHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/JsonParserHelper.java @@ -386,6 +386,7 @@ public class JsonParserHelper { ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.integer.format", "The parameter " + parameterName + " has value: " + numericalValueFormatted + " which is invalid integer value for provided locale of [" + clientApplicationLocale.toString() + "].", parameterName, numericalValueFormatted, clientApplicationLocale); + error.setValue(numericalValueFormatted); dataValidationErrors.add(error); throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", @@ -408,6 +409,7 @@ public class JsonParserHelper { List dataValidationErrors = new ArrayList(); ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.integer", "The parameter " + parameterName + " has value: " + numericalValueFormatted + " which is invalid integer.", parameterName, numericalValueFormatted); + error.setValue(numericalValueFormatted); dataValidationErrors.add(error); 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 " + parameterName + " has value: " + numericalValueFormatted + " which is invalid decimal value for provided locale of [" + clientApplicationLocale.toString() + "].", parameterName, numericalValueFormatted, clientApplicationLocale); + error.setValue(numericalValueFormatted); dataValidationErrors.add(error); throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/api/ReportsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/api/ReportsApiResource.java index 48103a295..7039067bf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/api/ReportsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/api/ReportsApiResource.java @@ -53,6 +53,7 @@ public class ReportsApiResource { this.genericDataService = genericDataService; this.toApiJsonSerializer = toApiJsonSerializer; } + @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON, "application/x-msdownload" }) @@ -144,10 +145,10 @@ public class ReportsApiResource { } else { json = this.toApiJsonSerializer.serializePretty(prettyPrint, result); } - + return Response.ok().entity(json).type(MediaType.APPLICATION_JSON).build(); } - + // CSV Export Map reportParams = getReportParams(queryParams, false); 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 // (dropdown listbox) values. - if (!parameterType) { AppUser currentUser = context.authenticatedUser(); - if (currentUser.hasNotPermissionForReport(reportName)) { - // FIXME - JW - message isnt passing back the message in the - // string in - // the json just a generalised not authorised message - throw new NoAuthorizationException("Not Authorised to Run Report: " + reportName); - } + if (currentUser.hasNotPermissionForReport(reportName)) { throw new NoAuthorizationException("Not authorised to run report: " + + reportName); } } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/DatatableData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/DatatableData.java index 8f9fea499..cf7ca9774 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/DatatableData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/DatatableData.java @@ -15,7 +15,11 @@ public class DatatableData { @SuppressWarnings("unused") 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.registeredTableName = registeredTableName; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/GenericResultsetData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/GenericResultsetData.java index 2aa184742..16dfa67f8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/GenericResultsetData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/GenericResultsetData.java @@ -13,19 +13,19 @@ import java.util.List; */ public class GenericResultsetData { - private final List columnHeaders; - private final List data; + private final List columnHeaders; + private final List data; - public GenericResultsetData(final List columnHeaders, final List resultsetDataRows) { + public GenericResultsetData(final List columnHeaders, final List resultsetDataRows) { this.columnHeaders = columnHeaders; this.data = resultsetDataRows; } - public List getColumnHeaders() { + public List getColumnHeaders() { return columnHeaders; } - public List getData() { + public List getData() { return data; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeader.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeader.java deleted file mode 100644 index 571344717..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeader.java +++ /dev/null @@ -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 columnValues = new ArrayList(); - - 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 getColumnValues() { - return columnValues; - } - - public void setColumnValues(List 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()); - } - -} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java new file mode 100644 index 000000000..aa9ba795b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java @@ -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 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 columnValues = new ArrayList(); + 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 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 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; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnValue.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnValue.java deleted file mode 100644 index 3f44556f8..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnValue.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnValueData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnValueData.java new file mode 100644 index 000000000..bdb4d7866 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnValueData.java @@ -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; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetDataRow.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetRowData.java similarity index 57% rename from mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetDataRow.java rename to mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetRowData.java index ff6530623..4c78a4d5f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetDataRow.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetRowData.java @@ -5,22 +5,21 @@ */ package org.mifosplatform.infrastructure.dataqueries.data; -import java.util.ArrayList; import java.util.List; -public class ResultsetDataRow { +public class ResultsetRowData { - private List row = new ArrayList(); + private final List row; - public ResultsetDataRow() { + public static ResultsetRowData create(final List rowValues) { + return new ResultsetRowData(rowValues); + } + private ResultsetRowData(final List rowValues) { + this.row = rowValues; } public List getRow() { return row; } - - public void setRow(List row) { - this.row = row; - } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataService.java index 4373147b3..7d0a43d1c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataService.java @@ -5,16 +5,13 @@ */ 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.ResultsetColumnHeaderData; public interface GenericDataService { - CachedRowSet getCachedResultSet(String sql, String errorMsg); - - void updateSQL(String sql, String sqlErrorMsg); - GenericResultsetData fillGenericResultSet(final String sql); String generateJsonFromGenericResultsetData(GenericResultsetData grs); @@ -25,4 +22,5 @@ public interface GenericDataService { String getDatabaseName(); + List fillResultsetColumnHeaders(String datatable); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataServiceImpl.java index aaea2b9da..62825f6f1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataServiceImpl.java @@ -5,128 +5,70 @@ */ 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.Statement; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; -import javax.sql.rowset.CachedRowSet; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource; import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData; -import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeader; -import org.mifosplatform.infrastructure.dataqueries.data.ResultsetDataRow; +import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeaderData; +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.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.jdbc.support.rowset.SqlRowSetMetaData; 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 public class GenericDataServiceImpl implements GenericDataService { + private final JdbcTemplate jdbcTemplate; private final DataSource dataSource; @Autowired public GenericDataServiceImpl(final TenantAwareRoutingDataSource dataSource) { - this.dataSource = 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); + this.jdbcTemplate = new JdbcTemplate(this.dataSource); } @Override public GenericResultsetData fillGenericResultSet(final String sql) { - String sqlErrorMsg = "Sql: " + sql; - CachedRowSet rs = getCachedResultSet(sql, sqlErrorMsg); + final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); - List columnHeaders = new ArrayList(); - List resultsetDataRows = new ArrayList(); + final List columnHeaders = new ArrayList(); + final List resultsetDataRows = new ArrayList(); - try { + final SqlRowSetMetaData rsmd = rs.getMetaData(); - ResultSetMetaData rsmd = rs.getMetaData(); - 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); - } + for (int i = 0; i < rsmd.getColumnCount(); i++) { - ResultsetDataRow resultsetDataRow; - while (rs.next()) { - resultsetDataRow = new ResultsetDataRow(); - List columnValues = new ArrayList(); - 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); - } + final String columnName = rsmd.getColumnName(i + 1); + final String columnType = rsmd.getColumnTypeName(i + 1); - return new GenericResultsetData(columnHeaders, resultsetDataRows); - } catch (SQLException e) { - throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), sqlErrorMsg); + final ResultsetColumnHeaderData columnHeader = ResultsetColumnHeaderData.basic(columnName, columnType); + columnHeaders.add(columnHeader); } + + while (rs.next()) { + final List columnValues = new ArrayList(); + 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 @@ -165,17 +107,16 @@ public class GenericDataServiceImpl implements GenericDataService { writer.append("["); - List columnHeaders = grs.getColumnHeaders(); - // logger.info("NO. of Columns: " + columnHeaders.size()); + final List columnHeaders = grs.getColumnHeaders(); - List data = grs.getData(); + List data = grs.getData(); List row; Integer rSize; String doubleQuote = "\""; String slashDoubleQuote = "\\\""; String currColType; String currVal; - // logger.info("NO. of Rows: " + data.size()); + for (int i = 0; i < data.size(); i++) { writer.append("\n{"); @@ -205,8 +146,8 @@ public class GenericDataServiceImpl implements GenericDataService { writer.append("[" + localDate.getYear() + ", " + localDate.getMonthOfYear() + ", " + localDate.getDayOfMonth() + "]"); } else { - - writer.append(doubleQuote + replace(currVal, doubleQuote,slashDoubleQuote) + doubleQuote); + + writer.append(doubleQuote + replace(currVal, doubleQuote, slashDoubleQuote) + doubleQuote); } } } else { @@ -217,8 +158,7 @@ public class GenericDataServiceImpl implements GenericDataService { if (i < (data.size() - 1)) { writer.append("},"); - } - else { + } else { writer.append("}"); } } @@ -227,24 +167,6 @@ public class GenericDataServiceImpl implements GenericDataService { 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 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"); } } + + @Override + public List fillResultsetColumnHeaders(final String datatable) { + + final SqlRowSet columnDefinitions = getDatatableMetaData(datatable); + + final List columnHeaders = new ArrayList(); + + 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 columnValues = new ArrayList(); + 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 retreiveColumnValues(final String columnName, final String code_suffix) { + + final List columnValues = new ArrayList(); + + 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); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadReportingServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadReportingServiceImpl.java index 118bf078c..c80c94da1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadReportingServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadReportingServiceImpl.java @@ -13,13 +13,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.sql.Date; -import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.Set; import javax.sql.DataSource; -import javax.sql.rowset.CachedRowSet; import javax.ws.rs.core.Response; 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.TenantAwareRoutingDataSource; import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData; -import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeader; -import org.mifosplatform.infrastructure.dataqueries.data.ResultsetDataRow; +import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeaderData; +import org.mifosplatform.infrastructure.dataqueries.data.ResultsetRowData; import org.mifosplatform.infrastructure.dataqueries.exception.ReportNotFoundException; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.useradministration.domain.AppUser; @@ -49,6 +47,8 @@ import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 com.lowagie.text.Document; @@ -59,467 +59,373 @@ import com.lowagie.text.pdf.PdfWriter; @Service public class ReadReportingServiceImpl implements ReadReportingService { - private final DataSource dataSource; - private final PlatformSecurityContext context; - - private final static Logger logger = LoggerFactory - .getLogger(ReadReportingServiceImpl.class); - - private Boolean noPentaho = false; - - @Autowired - public ReadReportingServiceImpl(final PlatformSecurityContext context, - final TenantAwareRoutingDataSource dataSource) { - // kick off pentaho reports server - ClassicEngineBoot.getInstance().start(); - noPentaho = false; - - this.context = context; - this.dataSource = dataSource; - } - - @Autowired - private GenericDataService genericDataService; - - @Override - public StreamingOutput retrieveReportCSV(final String name, - final String type, final Map queryParams) { - - return new StreamingOutput() { - - @Override - public void write(final OutputStream out) { - try { - - GenericResultsetData result = retrieveGenericResultset( - name, type, queryParams); - StringBuffer sb = generateCsvFileBuffer(result); - - InputStream in = new ByteArrayInputStream(sb.toString() - .getBytes("UTF-8")); - - byte[] outputByte = new byte[4096]; - Integer readLen = in.read(outputByte, 0, 4096); - - while (readLen != -1) { - out.write(outputByte, 0, readLen); - readLen = in.read(outputByte, 0, 4096); - } - // in.close(); - // 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 columnHeaders = result.getColumnHeaders(); - logger.info("NO. of Columns: " + columnHeaders.size()); - Integer chSize = columnHeaders.size(); - for (int i = 0; i < chSize; i++) { - writer.append('"' + columnHeaders.get(i).getColumnName() + '"'); - if (i < (chSize - 1)) - writer.append(","); - } - writer.append('\n'); - - List data = result.getData(); - List row; - Integer rSize; - // String currCol; - String currColType; - String currVal; - String doubleQuote = "\""; - String twoDoubleQuotes = doubleQuote + doubleQuote; - 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++) { - // currCol = columnHeaders.get(j).getColumnName(); - 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")) - writer.append(currVal); - else - writer.append('"' + genericDataService.replace(currVal, - doubleQuote, twoDoubleQuotes) + '"'); - - } - if (j < (rSize - 1)) - writer.append(","); - } - writer.append('\n'); - } - - return writer; - } - - @Override - public GenericResultsetData retrieveGenericResultset(final String name, - final String type, final Map queryParams) { - - long startTime = System.currentTimeMillis(); - logger.info("STARTING REPORT: " + name + " Type: " + type); - - String sql; - if (name.equals(".")) { - // 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 - // 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" - + " from stretchy_report r" - + " left join stretchy_report_parameter rp on rp.report_id = r.report_id" - + " left join stretchy_parameter p on p.parameter_id = rp.parameter_id" - + " where exists" - + " (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" - + " where ur.appuser_id = " - + context.authenticatedUser().getId() - + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', r.report_name))) " - + " order by r.report_name, rp.parameter_id"; - } else { - sql = getSQLtoRun(name, type, queryParams); - } - - GenericResultsetData result = genericDataService - .fillGenericResultSet(sql); - - long elapsed = System.currentTimeMillis() - startTime; - logger.info("FINISHING Report/Request Name: " + name + " - " + type - + " Elapsed Time: " + elapsed); - return result; - } - - private String getSQLtoRun(final String name, final String type, - final Map queryParams) { - - String sql = getSql(name, type); - - Set keys = queryParams.keySet(); - for (String key : keys) { - String pValue = queryParams.get(key); - // logger.info("(" + key + " : " + pValue + ")"); - sql = genericDataService.replace(sql, key, pValue); - } - - AppUser currentUser = context.authenticatedUser(); - // Allows sql query to restrict data by office hierarchy if required - sql = genericDataService.replace(sql, "${currentUserHierarchy}", - currentUser.getOffice().getHierarchy()); - // Allows sql query to restrict data by current user Id if required - // (typically used to return report lists containing only reports - // permitted to be run by the user - sql = genericDataService.replace(sql, "${currentUserId}", currentUser - .getId().toString()); - - sql = genericDataService.wrapSQL(sql); - - return sql; - - } - - private String getSql(final String name, final String type) { - - String inputSql = "select " + type + "_sql as the_sql from stretchy_" - + type + " where " + type + "_name = '" + name + "'"; - inputSql = genericDataService.wrapSQL(inputSql); - - String sqlErrorMsg = "Sql: " + inputSql; - CachedRowSet rs = genericDataService.getCachedResultSet(inputSql, - sqlErrorMsg); - - try { - if (rs.next()) { - return rs.getString("the_sql"); - } - throw new ReportNotFoundException(inputSql); - - } catch (SQLException e) { - logger.info("msg: " + e.getMessage() + " sql: " + inputSql); - throw new PlatformDataIntegrityException("error.msg.sql.error", - e.getMessage(), sqlErrorMsg); - } - - } - - @Override - public String getReportType(final String reportName) { - String sql = "SELECT ifnull(report_type,'') as report_type FROM `stretchy_report` where report_name = '" - + reportName + "'"; - - sql = genericDataService.wrapSQL(sql); - - String sqlErrorMsg = "Report Name: " + reportName + " Sql: " + sql; - CachedRowSet rs = genericDataService.getCachedResultSet(sql, - sqlErrorMsg); - - try { - - if (rs.next()) { - return rs.getString("report_type"); - } - throw new ReportNotFoundException(sql); - - } catch (SQLException e) { - throw new PlatformDataIntegrityException("error.msg.sql.error", - e.getMessage(), sqlErrorMsg); - } - - } - - @Override - public Response processPentahoRequest(final String reportName, - final String outputTypeParam, final Map queryParams) { - - String outputType = "HTML"; - if (StringUtils.isNotBlank(outputTypeParam)) - outputType = outputTypeParam; - - if (!(outputType.equalsIgnoreCase("HTML") - || outputType.equalsIgnoreCase("PDF") - || outputType.equalsIgnoreCase("XLS") || outputType - .equalsIgnoreCase("CSV"))) - throw new PlatformDataIntegrityException( - "error.msg.invalid.outputType", "No matching Output Type: " - + outputType); - - if (noPentaho) - throw new PlatformDataIntegrityException("error.msg.no.pentaho", - "Pentaho is not enabled", "Pentaho is not enabled"); - - // TODO - JW - use pentaho location finder like Pawel does in Mifos - // String reportPath = - // "C:\\dev\\apache-tomcat-7.0.25\\webapps\\ROOT\\PentahoReports\\" - // + reportName + ".prpt"; - /*String reportPath = FileUtils.MIFOSX_BASE_DIR - + File.separator - + ThreadLocalContextUtil.getTenant().getName() - .replaceAll(" ", "").trim() + File.separator - + "reports" + File.separator + reportName + ".prpt";*/ - String reportPath = FileUtils.MIFOSX_BASE_DIR - + File.separator - + "reports" + File.separator + reportName + ".prpt"; - logger.info("Report path: " + reportPath); - - // load report definition - ResourceManager manager = new ResourceManager(); - manager.registerDefaults(); - Resource res; - - try { - res = manager.createDirectly(reportPath, MasterReport.class); - MasterReport masterReport = (MasterReport) res.getResource(); - - addParametersToReport(masterReport, queryParams); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - if ("PDF".equalsIgnoreCase(outputType)) { - PdfReportUtil.createPDF(masterReport, baos); - return Response.ok().entity(baos.toByteArray()) - .type("application/pdf").build(); - } - - 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(); - } - - if ("CSV".equalsIgnoreCase(outputType)) { - CSVReportUtil.createCSV(masterReport, baos, "UTF-8"); - return Response - .ok() - .entity(baos.toByteArray()) - .type("application/x-msdownload") - .header("Content-Disposition", - "attachment;filename=" - + reportName.replaceAll(" ", "") - + ".csv").build(); - } - - if ("HTML".equalsIgnoreCase(outputType)) { - HtmlReportUtil.createStreamHTML(masterReport, baos); - return Response.ok().entity(baos.toByteArray()) - .type("text/html").build(); - } - } catch (ResourceException e) { - throw new PlatformDataIntegrityException( - "error.msg.reporting.error", e.getMessage()); - } catch (ReportProcessingException e) { - throw new PlatformDataIntegrityException( - "error.msg.reporting.error", e.getMessage()); - } catch (IOException e) { - throw new PlatformDataIntegrityException( - "error.msg.reporting.error", e.getMessage()); - } - - throw new PlatformDataIntegrityException( - "error.msg.invalid.outputType", "No matching Output Type: " - + outputType); - - } - - private void addParametersToReport(final MasterReport report, - final Map queryParams) { - - AppUser currentUser = context.authenticatedUser(); - - try { - - logger.info("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); - ReportParameterValues rptParamValues = report.getParameterValues(); - ReportParameterDefinition paramsDefinition = report - .getParameterDefinition(); - - /* - * only allow integer, long, date and string parameter types and - * assume all mandatory - could go more detailed like Pawel did in - * Mifos later and could match incoming and pentaho parameters - * better... currently assuming they come in ok... and if not an - * error - */ - for (ParameterDefinitionEntry paramDefEntry : paramsDefinition - .getParameterDefinitions()) { - String paramName = paramDefEntry.getName(); - if (!((paramName.equals("tenantdb")) || (paramName.equals("userhierarchy")))) { - logger.info("paramName:" + paramName); - String pValue = queryParams.get(paramName); - if (StringUtils.isBlank(pValue)) - throw new PlatformDataIntegrityException( - "error.msg.reporting.error", - "Pentaho Parameter: " + paramName - + " - not Provided"); - - Class clazz = paramDefEntry.getValueType(); - logger.info("addParametersToReport(" + paramName + " : " - + pValue + " : " + clazz.getCanonicalName() + ")"); - if (clazz.getCanonicalName().equalsIgnoreCase( - "java.lang.Integer")) - rptParamValues.put(paramName, Integer.parseInt(pValue)); - else if (clazz.getCanonicalName().equalsIgnoreCase( - "java.lang.Long")) - rptParamValues.put(paramName, Long.parseLong(pValue)); - else if (clazz.getCanonicalName().equalsIgnoreCase( - "java.sql.Date")) - 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 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 columnHeaders = result - .getColumnHeaders(); - List data = result.getData(); - List 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()); - } - } + private final static Logger logger = LoggerFactory.getLogger(ReadReportingServiceImpl.class); + + private final JdbcTemplate jdbcTemplate; + private final DataSource dataSource; + private final PlatformSecurityContext context; + private final GenericDataService genericDataService; + private boolean noPentaho = false; + + @Autowired + public ReadReportingServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource, + final GenericDataService genericDataService) { + // kick off pentaho reports server + ClassicEngineBoot.getInstance().start(); + noPentaho = false; + + this.context = context; + this.dataSource = dataSource; + this.jdbcTemplate = new JdbcTemplate(this.dataSource); + this.genericDataService = genericDataService; + } + + @Override + public StreamingOutput retrieveReportCSV(final String name, final String type, final Map queryParams) { + + return new StreamingOutput() { + + @Override + public void write(final OutputStream out) { + try { + + GenericResultsetData result = retrieveGenericResultset(name, type, queryParams); + StringBuffer sb = generateCsvFileBuffer(result); + + InputStream in = new ByteArrayInputStream(sb.toString().getBytes("UTF-8")); + + byte[] outputByte = new byte[4096]; + Integer readLen = in.read(outputByte, 0, 4096); + + while (readLen != -1) { + out.write(outputByte, 0, readLen); + readLen = in.read(outputByte, 0, 4096); + } + // in.close(); + // 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 columnHeaders = result.getColumnHeaders(); + logger.info("NO. of Columns: " + columnHeaders.size()); + Integer chSize = columnHeaders.size(); + for (int i = 0; i < chSize; i++) { + writer.append('"' + columnHeaders.get(i).getColumnName() + '"'); + if (i < (chSize - 1)) writer.append(","); + } + writer.append('\n'); + + List data = result.getData(); + List row; + Integer rSize; + // String currCol; + String currColType; + String currVal; + String doubleQuote = "\""; + String twoDoubleQuotes = doubleQuote + doubleQuote; + 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++) { + // currCol = columnHeaders.get(j).getColumnName(); + 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")) + writer.append(currVal); + else + writer.append('"' + genericDataService.replace(currVal, doubleQuote, twoDoubleQuotes) + '"'); + + } + if (j < (rSize - 1)) writer.append(","); + } + writer.append('\n'); + } + + return writer; + } + + @Override + public GenericResultsetData retrieveGenericResultset(final String name, final String type, final Map queryParams) { + + long startTime = System.currentTimeMillis(); + logger.info("STARTING REPORT: " + name + " Type: " + type); + + String sql; + if (name.equals(".")) { + // 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 + // 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" + " from stretchy_report r" + + " left join stretchy_report_parameter rp on rp.report_id = r.report_id" + + " left join stretchy_parameter p on p.parameter_id = rp.parameter_id" + " where exists" + " (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" + + " where ur.appuser_id = " + context.authenticatedUser().getId() + + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', r.report_name))) " + + " order by r.report_name, rp.parameter_id"; + } else { + sql = getSQLtoRun(name, type, queryParams); + } + + GenericResultsetData result = genericDataService.fillGenericResultSet(sql); + + long elapsed = System.currentTimeMillis() - startTime; + logger.info("FINISHING Report/Request Name: " + name + " - " + type + " Elapsed Time: " + elapsed); + return result; + } + + private String getSQLtoRun(final String name, final String type, final Map queryParams) { + + String sql = getSql(name, type); + + Set keys = queryParams.keySet(); + for (String key : keys) { + String pValue = queryParams.get(key); + // logger.info("(" + key + " : " + pValue + ")"); + sql = genericDataService.replace(sql, key, pValue); + } + + AppUser currentUser = context.authenticatedUser(); + // Allows sql query to restrict data by office hierarchy if required + sql = genericDataService.replace(sql, "${currentUserHierarchy}", currentUser.getOffice().getHierarchy()); + // Allows sql query to restrict data by current user Id if required + // (typically used to return report lists containing only reports + // permitted to be run by the user + sql = genericDataService.replace(sql, "${currentUserId}", currentUser.getId().toString()); + + sql = genericDataService.wrapSQL(sql); + + return sql; + + } + + private String getSql(final String name, final String type) { + + final String inputSql = "select " + type + "_sql as the_sql from stretchy_" + type + " where " + type + "_name = '" + name + "'"; + final String inputSqlWrapped = genericDataService.wrapSQL(inputSql); + + final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(inputSqlWrapped); + + if (rs.next()) { return rs.getString("the_sql"); } + throw new ReportNotFoundException(inputSql); + } + + @Override + public String getReportType(final String reportName) { + + final String sql = "SELECT ifnull(report_type,'') as report_type FROM `stretchy_report` where report_name = '" + reportName + "'"; + + final String sqlWrapped = genericDataService.wrapSQL(sql); + + final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sqlWrapped); + + if (rs.next()) { return rs.getString("report_type"); } + throw new ReportNotFoundException(sql); + } + + @Override + public Response processPentahoRequest(final String reportName, final String outputTypeParam, final Map queryParams) { + + String outputType = "HTML"; + if (StringUtils.isNotBlank(outputTypeParam)) outputType = outputTypeParam; + + if (!(outputType.equalsIgnoreCase("HTML") || outputType.equalsIgnoreCase("PDF") || outputType.equalsIgnoreCase("XLS") || outputType + .equalsIgnoreCase("CSV"))) + throw new PlatformDataIntegrityException("error.msg.invalid.outputType", "No matching Output Type: " + outputType); + + if (noPentaho) { throw new PlatformDataIntegrityException("error.msg.no.pentaho", "Pentaho is not enabled", + "Pentaho is not enabled"); } + + // TODO - JW - use pentaho location finder like Pawel does in Mifos + // String reportPath = + // "C:\\dev\\apache-tomcat-7.0.25\\webapps\\ROOT\\PentahoReports\\" + // + reportName + ".prpt"; + /* + * String reportPath = FileUtils.MIFOSX_BASE_DIR + File.separator + + * ThreadLocalContextUtil.getTenant().getName() .replaceAll(" ", + * "").trim() + File.separator + "reports" + File.separator + reportName + * + ".prpt"; + */ + final String reportPath = FileUtils.MIFOSX_BASE_DIR + File.separator + "reports" + File.separator + reportName + ".prpt"; + logger.info("Report path: " + reportPath); + + // load report definition + ResourceManager manager = new ResourceManager(); + manager.registerDefaults(); + Resource res; + + try { + res = manager.createDirectly(reportPath, MasterReport.class); + MasterReport masterReport = (MasterReport) res.getResource(); + + addParametersToReport(masterReport, queryParams); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + if ("PDF".equalsIgnoreCase(outputType)) { + PdfReportUtil.createPDF(masterReport, baos); + return Response.ok().entity(baos.toByteArray()).type("application/pdf").build(); + } + + 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(); + } + + if ("CSV".equalsIgnoreCase(outputType)) { + CSVReportUtil.createCSV(masterReport, baos, "UTF-8"); + return Response.ok().entity(baos.toByteArray()).type("application/x-msdownload") + .header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".csv").build(); + } + + if ("HTML".equalsIgnoreCase(outputType)) { + HtmlReportUtil.createStreamHTML(masterReport, baos); + return Response.ok().entity(baos.toByteArray()).type("text/html").build(); + } + } catch (ResourceException e) { + throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage()); + } catch (ReportProcessingException e) { + throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage()); + } catch (IOException e) { + throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage()); + } + + throw new PlatformDataIntegrityException("error.msg.invalid.outputType", "No matching Output Type: " + outputType); + } + + private void addParametersToReport(final MasterReport report, final Map queryParams) { + + AppUser currentUser = context.authenticatedUser(); + + try { + + logger.info("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + ReportParameterValues rptParamValues = report.getParameterValues(); + ReportParameterDefinition paramsDefinition = report.getParameterDefinition(); + + /* + * only allow integer, long, date and string parameter types and + * assume all mandatory - could go more detailed like Pawel did in + * Mifos later and could match incoming and pentaho parameters + * better... currently assuming they come in ok... and if not an + * error + */ + for (ParameterDefinitionEntry paramDefEntry : paramsDefinition.getParameterDefinitions()) { + String paramName = paramDefEntry.getName(); + if (!((paramName.equals("tenantdb")) || (paramName.equals("userhierarchy")))) { + logger.info("paramName:" + paramName); + String pValue = queryParams.get(paramName); + if (StringUtils.isBlank(pValue)) + throw new PlatformDataIntegrityException("error.msg.reporting.error", "Pentaho Parameter: " + paramName + + " - not Provided"); + + Class clazz = paramDefEntry.getValueType(); + logger.info("addParametersToReport(" + paramName + " : " + pValue + " : " + clazz.getCanonicalName() + ")"); + if (clazz.getCanonicalName().equalsIgnoreCase("java.lang.Integer")) + rptParamValues.put(paramName, Integer.parseInt(pValue)); + else if (clazz.getCanonicalName().equalsIgnoreCase("java.lang.Long")) + rptParamValues.put(paramName, Long.parseLong(pValue)); + else if (clazz.getCanonicalName().equalsIgnoreCase("java.sql.Date")) + 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.error("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 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 columnHeaders = result.getColumnHeaders(); + List data = result.getData(); + List 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) { + logger.error("error.msg.reporting.error:" + e.getMessage()); + throw new PlatformDataIntegrityException("error.msg.exception.error", e.getMessage()); + } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java index 990f09186..b2fe582aa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java @@ -7,8 +7,6 @@ package org.mifosplatform.infrastructure.dataqueries.service; import java.lang.reflect.Type; import java.math.BigDecimal; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -16,7 +14,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import javax.sql.rowset.CachedRowSet; +import javax.sql.DataSource; import org.apache.commons.lang.StringUtils; 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.serialization.FromJsonHelper; 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.GenericResultsetData; -import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeader; -import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnValue; -import org.mifosplatform.infrastructure.dataqueries.data.ResultsetDataRow; +import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeaderData; +import org.mifosplatform.infrastructure.dataqueries.data.ResultsetRowData; import org.mifosplatform.infrastructure.dataqueries.exception.DataTableNotFoundException; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.useradministration.domain.AppUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 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 JdbcTemplate jdbcTemplate; + private final DataSource dataSource; private final PlatformSecurityContext context; private final FromJsonHelper fromJsonHelper; + private final JsonParserHelper helper; + private final GenericDataService genericDataService; @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.fromJsonHelper = fromJsonHelper; + this.helper = new JsonParserHelper(); + this.genericDataService = genericDataService; } - @Autowired - private GenericDataService genericDataService; - @Override public List retrieveDatatableNames(final String appTable) { @@ -67,25 +74,23 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } else { andClause = " and application_table_name = '" + appTable + "'"; } + // 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" + " 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() + " 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"; - String sqlErrorMsg = "Application Table Name: " + appTable + " sql: " + sql; - CachedRowSet rs = genericDataService.getCachedResultSet(sql, sqlErrorMsg); + final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); - List datatables = new ArrayList(); - try { - while (rs.next()) { - datatables.add(new DatatableData(rs.getString("application_table_name"), rs.getString("registered_table_name"))); - } + final List datatables = new ArrayList(); + while (rs.next()) { + final String appTableName = rs.getString("application_table_name"); + final String registeredDatatableName = rs.getString("registered_table_name"); - } catch (SQLException e) { - throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), sqlErrorMsg); + datatables.add(DatatableData.create(appTableName, registeredDatatableName)); } return datatables; @@ -96,63 +101,89 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ validateAppTable(appTable); - String createPermission = "'CREATE_" + datatable + "'"; - String createPermissionChecker = "'CREATE_" + datatable + "_CHECKER'"; - String readPermission = "'READ_" + datatable + "'"; - String updatePermission = "'UPDATE_" + datatable + "'"; - String updatePermissionChecker = "'UPDATE_" + datatable + "_CHECKER'"; - String deletePermission = "'DELETE_" + datatable + "'"; - 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 + "')"; + final String createPermission = "'CREATE_" + datatable + "'"; + final String createPermissionChecker = "'CREATE_" + datatable + "_CHECKER'"; + final String readPermission = "'READ_" + datatable + "'"; + final String updatePermission = "'UPDATE_" + datatable + "'"; + final String updatePermissionChecker = "'UPDATE_" + datatable + "_CHECKER'"; + final String deletePermission = "'DELETE_" + datatable + "'"; + final String deletePermissionChecker = "'DELETE_" + datatable + "_CHECKER'"; - genericDataService.updateSQL(sql, "SQL: " + sql); - /* - * add all permissions, including checker permissions for maintenance - * tasks - */ - sql = "insert into m_permission (grouping, code, action_name, entity_name, can_maker_checker) values " + "('datatable', " - + createPermission + ", 'CREATE', '" + 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 + // TODO - JW - put in batch command later + final String registerDatatableSql = "insert into x_registered_table (registered_table_name, application_table_name) values ('" + + datatable + "', '" + appTable + "')"; + + this.jdbcTemplate.update(registerDatatableSql); + + final String permissionsSql = "insert into m_permission (grouping, code, action_name, entity_name, can_maker_checker) values " + + "('datatable', " + + createPermission + + ", 'CREATE', '" + + 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)"; - genericDataService.updateSQL(sql, "SQL: " + sql); + this.jdbcTemplate.update(permissionsSql); } @Override public void deregisterDatatable(final String datatable) { // TODO - JW - put in batch command later - - String permissionList = "('CREATE_" + datatable + "', 'CREATE_" + datatable + "_CHECKER', 'READ_" + datatable + "', 'UPDATE_" + final String permissionList = "('CREATE_" + datatable + "', 'CREATE_" + datatable + "_CHECKER', 'READ_" + datatable + "', 'UPDATE_" + 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 " + permissionList + ")"; - genericDataService.updateSQL(sql, "SQL: " + sql); + this.jdbcTemplate.update(sql); 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 + "'"; - genericDataService.updateSQL(sql, "SQL: " + sql); + this.jdbcTemplate.update(sql); } @Override public void newDatatableEntry(final String datatable, final Long appTableId, final JsonCommand command) { String appTable = getWithinScopeApplicationTableName(datatable, appTableId); - List columnHeaders = getDatatableResultsetColumnHeaders(datatable); + final List columnHeaders = this.genericDataService.fillResultsetColumnHeaders(datatable); final Type typeOfMap = new TypeToken>() {}.getType(); - Map dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json()); + final Map 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 @@ -166,7 +197,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ throw new PlatformDataIntegrityException("error.msg.attempting.multiple.update", "Application Table: " + datatable + " Foreign Key Id: " + appTableId); - String fkName = getFKField(getApplicationTableName(datatable)); + String fkName = getFKField(queryForApplicationTableName(datatable)); final Type typeOfMap = new TypeToken>() {}.getType(); Map dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json()); @@ -175,10 +206,10 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ if (changes.size() == 0) return changes; - String sql = getUpdateSql(datatable, fkName, appTableId, changes); + final String sql = getUpdateSql(datatable, fkName, appTableId, changes); - if (sql != null) { - genericDataService.updateSQL(sql, "SQL: " + sql); + if (StringUtils.isNotBlank(sql)) { + this.jdbcTemplate.update(sql); } else { logger.info("No Changes"); } @@ -190,21 +221,21 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ public Map updateDatatableEntryOneToMany(final String datatable, final Long appTableId, final Long datatableId, 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); final Type typeOfMap = new TypeToken>() {}.getType(); - Map dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json()); + final Map dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json()); final Map changes = getAffectedAndChangedColumns(grs, dataParams, "id"); if (changes.size() == 0) return changes; - String sql = getUpdateSql(datatable, "id", datatableId, changes); + final String sql = getUpdateSql(datatable, "id", datatableId, changes); - if (sql != null) { - genericDataService.updateSQL(sql, "SQL: " + sql); + if (StringUtils.isNotBlank(sql)) { + this.jdbcTemplate.update(sql); } else { logger.info("No Changes"); } @@ -215,11 +246,11 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ @Override 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 @@ -227,18 +258,18 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ getWithinScopeApplicationTableName(datatable, appTableId); - String sql = getDeleteEntrySql(datatable, datatableId); + final String sql = getDeleteEntrySql(datatable, datatableId); - genericDataService.updateSQL(sql, "SQL: " + sql); + this.jdbcTemplate.update(sql); } @Override public GenericResultsetData retrieveDataTableGenericResultSet(final String datatable, final Long appTableId, final String order, final Long id) { - String appTable = getWithinScopeApplicationTableName(datatable, appTableId); + final String appTable = getWithinScopeApplicationTableName(datatable, appTableId); - List columnHeaders = getDatatableResultsetColumnHeaders(datatable); + final List columnHeaders = this.genericDataService.fillResultsetColumnHeaders(datatable); String sql = ""; @@ -252,20 +283,20 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ if (order != null) sql = sql + " order by " + order; - List result = fillDatatableResultSetDataRows(sql); + List result = fillDatatableResultSetDataRows(sql); return new GenericResultsetData(columnHeaders, result); } 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) { @@ -303,74 +334,50 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ throw new PlatformDataIntegrityException("error.msg.invalid.application.table", "Invalid Application Table: " + appTable); } - private List fillDatatableResultSetDataRows(final String sql) { + private List fillDatatableResultSetDataRows(final String sql) { - String sqlErrorMsg = "Sql: " + sql; - CachedRowSet rs = genericDataService.getCachedResultSet(sql, sqlErrorMsg); + final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); - List resultsetDataRows = new ArrayList(); + final List resultsetDataRows = new ArrayList(); - try { + final SqlRowSetMetaData rsmd = rs.getMetaData(); - ResultSetMetaData rsmd = rs.getMetaData(); - int columnCount = rsmd.getColumnCount(); - - ResultsetDataRow resultsetDataRow; - String columnName = null; - String columnValue = null; - while (rs.next()) { - resultsetDataRow = new ResultsetDataRow(); - List columnValues = new ArrayList(); - 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); + while (rs.next()) { + final List columnValues = new ArrayList(); + for (int i = 0; i < rsmd.getColumnCount(); i++) { + final String columnName = rsmd.getColumnName(i + 1); + final String columnValue = rs.getString(columnName); + columnValues.add(columnValue); } - return resultsetDataRows; - } catch (SQLException e) { - throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), sqlErrorMsg); + final ResultsetRowData resultsetDataRow = ResultsetRowData.create(columnValues); + resultsetDataRows.add(resultsetDataRow); } + + return resultsetDataRows; } 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); - - 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()); - } + return applicationTableName; } - private String getApplicationTableName(final String datatable) { - // TODO - JW - only used for update... can probably remove this after as - // its - // a reread - String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = '" + datatable + "'"; + private String queryForApplicationTableName(final String datatable) { + final 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); - - try { - rs.next(); - return rs.getString("application_table_name"); - } catch (SQLException e) { - throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage()); + String applicationTableName = null; + if (rs.next()) { + applicationTableName = rs.getString("application_table_name"); + } else { + throw new DataTableNotFoundException(datatable); } + + return applicationTableName; } private String getFKField(final String applicationTableName) { @@ -378,89 +385,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ return applicationTableName.substring(2) + "_id"; } - private CachedRowSet getDatatableMetaData(final String datatable) { - - 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 getDatatableResultsetColumnHeaders(final String datatable) { - - CachedRowSet columnDefinitions = getDatatableMetaData(datatable); - - List columnHeaders = new ArrayList(); - - 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 columnHeaders, final String datatable, final String fkName, + private String getAddSql(final List columnHeaders, final String datatable, final String fkName, final Long appTableId, final Map queryParams) { final Map 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"); } - private Map getAffectedColumns(final List columnHeaders, final Map queryParams, - final String keyFieldName) { + private Map getAffectedColumns(final List columnHeaders, + final Map queryParams, final String keyFieldName) { String dateFormat = queryParams.get("dateFormat"); Locale clientApplicationLocale = null; @@ -600,7 +525,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ // matches incoming fields with and without underscores (spaces // and underscores considered the same) queryParamColumnUnderscored = genericDataService.replace(key, space, underscore); - for (ResultsetColumnHeader columnHeader : columnHeaders) { + for (ResultsetColumnHeaderData columnHeader : columnHeaders) { if (notFound) { columnHeaderUnderscored = genericDataService.replace(columnHeader.getColumnName(), space, underscore); if (queryParamColumnUnderscored.equalsIgnoreCase(columnHeaderUnderscored)) { @@ -618,74 +543,84 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ 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) { String paramValue = pValue; - if ((columnHeader.getColumnDisplayType().equals("DATE")) || (columnHeader.getColumnDisplayType().equals("INTEGER")) - || (columnHeader.getColumnDisplayType().equals("DECIMAL"))) paramValue = paramValue.trim(); + if (columnHeader.isDateDisplayType() || columnHeader.isIntegerDisplayType() || columnHeader.isDecimalDisplayType()) { + paramValue = paramValue.trim(); + } - if ((StringUtils.isEmpty(paramValue)) && (!(columnHeader.isColumnNullable()))) { + if (StringUtils.isEmpty(paramValue) && columnHeader.isMandatory()) { - List dataValidationErrors = new ArrayList(); - ApiParameterError error = ApiParameterError.parameterError("error.msg.column.mandatory", "Mandatory", + final List dataValidationErrors = new ArrayList(); + final ApiParameterError error = ApiParameterError.parameterError("error.msg.column.mandatory", "Mandatory", columnHeader.getColumnName()); dataValidationErrors.add(error); throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); } - if (!StringUtils.isEmpty(paramValue)) { + if (StringUtils.isNotEmpty(paramValue)) { - if (columnHeader.getColumnValues().size() > 0) { - // match code value or id - List allowedValues = columnHeader.getColumnValues(); - if (columnHeader.getColumnDisplayType().equals("CODEVALUE")) { - for (ResultsetColumnValue allowedValue : allowedValues) { - if (paramValue.equalsIgnoreCase(allowedValue.getValue())) return paramValue; + if (columnHeader.hasColumnValues()) { + if (columnHeader.isCodeValueDisplayType()) { + + if (columnHeader.isColumnValueNotAllowed(paramValue)) { + final List dataValidationErrors = new ArrayList(); + 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 dataValidationErrors = new ArrayList(); - 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")) { - for (ResultsetColumnValue allowedValue : allowedValues) { - if (paramValue.equals(Integer.toString(allowedValue.getId()))) return paramValue; + return paramValue; + } else if (columnHeader.isCodeLookupDisplayType()) { + + final Integer codeLookup = Integer.valueOf(paramValue); + if (columnHeader.isColumnCodeNotAllowed(codeLookup)) { + final List dataValidationErrors = new ArrayList(); + 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 dataValidationErrors = new ArrayList(); - 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() - + " - Invalid Type " + columnHeader.getColumnType() + " (neither varchar nor int)"); + return paramValue; + } 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.getColumnDisplayType().equals("DATE")) { - LocalDate tmpDate = helper.convertFrom(paramValue, columnHeader.getColumnName(), dateFormat, clientApplicationLocale); - if (tmpDate == null) return null; - return tmpDate.toString(); + if (columnHeader.isDateDisplayType()) { + final LocalDate tmpDate = helper.convertFrom(paramValue, columnHeader.getColumnName(), dateFormat, clientApplicationLocale); + if (tmpDate == null) { + paramValue = null; + } else { + paramValue = tmpDate.toString(); + } } - if (columnHeader.getColumnDisplayType().equals("INTEGER")) { + + if (columnHeader.isIntegerDisplayType()) { Integer tmpInt = helper.convertToInteger(paramValue, columnHeader.getColumnName(), clientApplicationLocale); - if (tmpInt == null) return null; - return tmpInt.toString(); - } - if (columnHeader.getColumnDisplayType().equals("DECIMAL")) { - BigDecimal tmpDecimal = helper.convertFrom(paramValue, columnHeader.getColumnName(), clientApplicationLocale); - if (tmpDecimal == null) return null; - return tmpDecimal.toString(); + if (tmpInt == null) { + paramValue = null; + } else { + paramValue = tmpInt.toString(); + } } + if (columnHeader.isDecimalDisplayType()) { + BigDecimal tmpDecimal = helper.convertFrom(paramValue, columnHeader.getColumnName(), clientApplicationLocale); + if (tmpDecimal == null) { + paramValue = null; + } else { + paramValue = tmpDecimal.toString(); + } + } } return paramValue;