refactor noncore reporting and additional data services

This commit is contained in:
John Woodlock
2012-08-28 16:06:00 +10:00
parent 72d854b0cd
commit 0474b5df0c
10 changed files with 1129 additions and 997 deletions
@@ -1,965 +0,0 @@
package org.mifosng.platform;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.Date;
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 java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import org.apache.commons.lang.StringUtils;
import org.mifosng.platform.api.data.AdditionalFieldsSetData;
import org.mifosng.platform.api.data.GenericResultsetData;
import org.mifosng.platform.api.data.ResultsetColumnHeader;
import org.mifosng.platform.api.data.ResultsetDataRow;
import org.mifosng.platform.exceptions.AdditionalFieldsNotFoundException;
import org.mifosng.platform.exceptions.PlatformDataIntegrityException;
import org.mifosng.platform.exceptions.ReportNotFoundException;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.PdfReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.csv.CSVReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.xls.ExcelReportUtil;
import org.pentaho.reporting.engine.classic.core.parameters.ParameterDefinitionEntry;
import org.pentaho.reporting.engine.classic.core.parameters.ReportParameterDefinition;
import org.pentaho.reporting.engine.classic.core.util.ReportParameterValues;
import org.pentaho.reporting.libraries.resourceloader.Resource;
import org.pentaho.reporting.libraries.resourceloader.ResourceException;
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.stereotype.Service;
@Service
public class ReadExtraDataAndReportingServiceImpl implements
ReadExtraDataAndReportingService {
private final PlatformSecurityContext context;
private final static Logger logger = LoggerFactory
.getLogger(ReadExtraDataAndReportingServiceImpl.class);
private final DataSource dataSource;
private Boolean noPentaho = false;
@Autowired
public ReadExtraDataAndReportingServiceImpl(
final PlatformSecurityContext context,
final TenantAwareRoutingDataSource dataSource) {
// kick off pentaho reports server
ClassicEngineBoot.getInstance().start();
noPentaho = false;
this.context = context;
this.dataSource = dataSource;
}
@Override
public StreamingOutput retrieveReportCSV(final String name,
final String type, final Map<String, String> queryParams) {
return new StreamingOutput() {
@Override
public void write(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 static StringBuffer generateCsvFileBuffer(
GenericResultsetData result) {
StringBuffer writer = new StringBuffer();
List<ResultsetColumnHeader> 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<ResultsetDataRow> data = result.getData();
List<String> row;
Integer rSize;
// String currCol;
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++) {
// 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('"' + currVal + '"');
}
if (j < (rSize - 1))
writer.append(",");
}
writer.append('\n');
}
return writer;
}
@Override
public GenericResultsetData retrieveGenericResultset(final String name,
final String type, final Map<String, String> 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 bring back 'permitted' reports via this api call.
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 (r.name = 'Super User' or r.name = 'Read Only') or p.code = concat('CAN_RUN_', r.report_name))"
+ " order by r.report_name, rp.parameter_id";
} else {
sql = getSQLtoRun(name, type, queryParams);
}
GenericResultsetData result = fillReportingGenericResultSet(sql);
long elapsed = System.currentTimeMillis() - startTime;
logger.info("FINISHING Report/Request Name: " + name + " - " + type
+ " Elapsed Time: " + elapsed);
return result;
}
private GenericResultsetData fillReportingGenericResultSet(final String sql) {
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
ResultSet rs = db_statement.executeQuery(sql);
List<ResultsetColumnHeader> columnHeaders = new ArrayList<ResultsetColumnHeader>();
List<ResultsetDataRow> resultsetDataRows = new ArrayList<ResultsetDataRow>();
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);
}
ResultsetDataRow resultsetDataRow;
while (rs.next()) {
resultsetDataRow = new ResultsetDataRow();
List<String> columnValues = new ArrayList<String>();
for (int i = 0; i < rsmd.getColumnCount(); i++) {
columnName = rsmd.getColumnName(i + 1);
columnValue = rs.getString(columnName);
columnValues.add(columnValue);
}
resultsetDataRow.setRow(columnValues);
resultsetDataRows.add(resultsetDataRow);
}
return new GenericResultsetData(columnHeaders, resultsetDataRows);
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Sql: " + sql);
} finally {
dbClose(db_statement, db_connection);
}
}
private String getSQLtoRun(final String name, final String type,
final Map<String, String> queryParams) {
String sql = null;
if (type.equals("report")) {
sql = getReportSql(name);
} else {
sql = getParameterSql(name);
}
Set<String> keys = queryParams.keySet();
for (String key : keys) {
String pValue = queryParams.get(key);
// logger.info("(" + key + " : " + pValue + ")");
sql = replace(sql, key, pValue);
}
AppUser currentUser = context.authenticatedUser();
// Allows sql query to restrict data by office hierarchy if required
sql = 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 = replace(sql, "${currentUserId}", currentUser.getId().toString());
// wrap sql to prevent JDBC sql errors and also prevent malicious sql
sql = "select x.* from (" + sql + ") x";
return sql;
}
private String getReportSql(String reportName) {
String sql = "select report_sql as the_sql from stretchy_report where report_name = '"
+ reportName + "'";
return getSql(sql);
}
private String getParameterSql(String parameterName) {
String sql = "select parameter_sql as the_sql from stretchy_parameter where parameter_name = '"
+ parameterName + "'";
return getSql(sql);
}
private String getSql(String inputSql) {
String sql = null;
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
ResultSet rs = db_statement.executeQuery(inputSql);
if (rs.next()) {
sql = rs.getString("the_sql");
} else {
throw new ReportNotFoundException(inputSql);
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Input Sql: " + inputSql);
} finally {
dbClose(db_statement, db_connection);
}
return sql;
}
@Override
public List<AdditionalFieldsSetData> retrieveExtraDatasetNames(String type) {
List<AdditionalFieldsSetData> additionalFieldsSets = new ArrayList<AdditionalFieldsSetData>();
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
String andClause;
if (type == null) {
andClause = "";
} else {
andClause = " and t.`name` = '" + type + "'";
}
String sql = "select d.id, d.`name` as 'set', t.`name` as 'type' "
+ " from stretchydata_dataset d join stretchydata_datasettype t on t.id = d.datasettype_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 (r.name = 'Super User' or r.name = 'Read Only') or p.code = concat('CAN_READ_', t.`name`, '_x', d.`name`)) "
+ andClause + " order by d.`name`";
ResultSet rs = db_statement.executeQuery(sql);
while (rs.next()) {
additionalFieldsSets.add(new AdditionalFieldsSetData(rs
.getInt("id"), rs.getString("set"), rs
.getString("type")));
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Additional Fields Type: " + type);
} finally {
dbClose(db_statement, db_connection);
}
return additionalFieldsSets;
}
@Override
public String retrieveDataTable(String datatable, String sqlFields,
String sqlSearch, String sqlOrder) {
long startTime = System.currentTimeMillis();
String sql = "select ";
if (sqlFields != null)
sql = sql + sqlFields;
else
sql = sql + " * ";
sql = sql + " from " + datatable;
if (sqlSearch != null)
sql = sql + " where " + sqlSearch;
if (sqlOrder != null)
sql = sql + " order by " + sqlOrder;
GenericResultsetData result = fillReportingGenericResultSet(sql);
String jsonString = generateJsonFromGenericResultsetData(result);
logger.info("JSON is: " + jsonString);
long elapsed = System.currentTimeMillis() - startTime;
logger.info("FINISHING DATATABLE: " + datatable + " Elapsed Time: "
+ elapsed);
return jsonString;
}
private static String generateJsonFromGenericResultsetData(
GenericResultsetData result) {
StringBuffer writer = new StringBuffer();
writer.append("[");
List<ResultsetColumnHeader> columnHeaders = result.getColumnHeaders();
logger.info("NO. of Columns: " + columnHeaders.size());
List<ResultsetDataRow> data = result.getData();
List<String> row;
Integer rSize;
String currColType;
String currVal;
logger.info("NO. of Rows: " + data.size());
for (int i = 0; i < data.size(); i++) {
writer.append("\n{");
row = data.get(i).getRow();
rSize = row.size();
for (int j = 0; j < rSize; j++) {
writer.append('\"' + 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('\"' + currVal + '\"');
} else
writer.append("null");
if (j < (rSize - 1))
writer.append(",\n");
}
if (i < (data.size() - 1))
writer.append("},");
else
writer.append("}");
}
writer.append("\n]");
return writer.toString();
/*
* JSONObject js = null; try { js = new JSONObject(writer.toString()); }
* catch (JSONException e) { throw new WebApplicationException(Response
* .status(Status.BAD_REQUEST).entity("JSON body is wrong") .build()); }
*
* return js.toString();
*/
}
@Override
public GenericResultsetData retrieveExtraData(String type, String set,
Long id) {
long startTime = System.currentTimeMillis();
GenericResultsetData result = fillExtraDataGenericResultSet(type, set,
id);
long elapsed = System.currentTimeMillis() - startTime;
logger.info("FINISHING SET: " + set + " Elapsed Time: " + elapsed);
return result;
}
private GenericResultsetData fillExtraDataGenericResultSet(
final String type, final String set, final Long id) {
Connection db_connection = null;
Statement db_statement1 = null;
Statement db_statement2 = null;
Statement db_statement3 = null;
try {
db_connection = dataSource.getConnection();
db_statement1 = db_connection.createStatement();
String sql = "select f.`name`, f.data_type, f.data_length, f.display_type, f.allowed_list_id from stretchydata_datasettype t join stretchydata_dataset d on d.datasettype_id = t.id join stretchydata_dataset_fields f on f.dataset_id = d.id where d.`name` = '"
+ set + "' and t.`name` = '" + type + "' order by f.id";
ResultSet rsmd = db_statement1.executeQuery(sql);
List<ResultsetColumnHeader> columnHeaders = null;
List<ResultsetDataRow> resultsetDataRows = null;
if (rsmd.next()) {
String fullDatasetName = getFullDatasetName(type, set);
db_statement2 = db_connection.createStatement();
columnHeaders = new ArrayList<ResultsetColumnHeader>();
Boolean firstColumn = true;
Integer allowedListId;
String selectFieldList = "";
String selectFieldSeparator = "";
do {
ResultsetColumnHeader rsch = new ResultsetColumnHeader();
rsch.setColumnName(rsmd.getString("name"));
if (firstColumn) {
selectFieldSeparator = " ";
firstColumn = false;
} else {
selectFieldSeparator = ", ";
}
selectFieldList += selectFieldSeparator + "s.`"
+ rsch.getColumnName() + "`";
rsch.setColumnType(rsmd.getString("data_type"));
if (rsmd.getInt("data_length") > 0)
rsch.setColumnLength(rsmd.getInt("data_length"));
rsch.setColumnDisplayType(rsmd.getString("display_type"));
allowedListId = rsmd.getInt("allowed_list_id");
if (allowedListId > 0) {
// logger.info("allowedListId != null: Column: " +
// rsch.getColumnName());
sql = "select v.`name` from stretchydata_allowed_value v where allowed_list_id = "
+ allowedListId + " order by id";
ResultSet rsValues = db_statement2.executeQuery(sql);
while (rsValues.next()) {
rsch.getColumnValues().add(
rsValues.getString("name"));
}
}
columnHeaders.add(rsch);
} while (rsmd.next());
String unScopedSQL = "select " + selectFieldList + " from `"
+ type + "` t ${dataScopeCriteria} left join `"
+ fullDatasetName + "` s on s.id = t.id "
+ " where t.id = " + id;
sql = dataScopedSQL(unScopedSQL, type);
logger.info("addition fields sql: " + sql);
db_statement3 = db_connection.createStatement();
ResultSet rs = db_statement3.executeQuery(sql);
if (rs.next()) {
String columnName = null;
String columnValue = null;
resultsetDataRows = new ArrayList<ResultsetDataRow>();
ResultsetDataRow resultsetDataRow;
do {
resultsetDataRow = new ResultsetDataRow();
List<String> columnValues = new ArrayList<String>();
for (int i = 0; i < columnHeaders.size(); i++) {
columnName = columnHeaders.get(i).getColumnName();
columnValue = rs.getString(columnName);
columnValues.add(columnValue);
}
resultsetDataRow.setRow(columnValues);
resultsetDataRows.add(resultsetDataRow);
} while (rs.next());
} else {
// could also be not found because of data scope
throw new AdditionalFieldsNotFoundException(type, id);
}
} else {
throw new AdditionalFieldsNotFoundException(type, set);
}
return new GenericResultsetData(columnHeaders, resultsetDataRows);
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Additional Fields Type: " + type
+ " Set: " + set + " Id: " + id);
} finally {
dbClose(db_statement2, null);
dbClose(db_statement3, null);
dbClose(db_statement1, db_connection);
}
}
private String dataScopedSQL(String unScopedSQL, String type) {
String dataScopeCriteria = null;
/*
* unfortunately have to, one way or another, be able to restrict data
* to the users office hierarchy. Here it's hardcoded for client and
* loan. They are the main application tables. But if additional fields
* are needed on other tables like group, loan_transaction or others the
* same applies (hardcoding of some sort)
*/
AppUser currentUser = context.authenticatedUser();
if (type.equalsIgnoreCase("m_client")) {
dataScopeCriteria = " join m_office o on o.id = t.office_id and o.hierarchy like '"
+ currentUser.getOffice().getHierarchy() + "%'";
}
if (type.equalsIgnoreCase("m_loan")) {
dataScopeCriteria = " join m_client c on c.id = t.client_id "
+ " join m_office o on o.id = c.office_id and o.hierarchy like '"
+ currentUser.getOffice().getHierarchy() + "%'";
}
if (dataScopeCriteria == null) {
throw new PlatformDataIntegrityException(
"error.msg.invalid.dataScopeCriteria", "Type: " + type
+ " not catered for in data Scoping");
}
return replace(unScopedSQL, "${dataScopeCriteria}", dataScopeCriteria);
}
private String getFullDatasetName(final String type, final String set) {
return type + "_x" + set;
}
private static String replace(String str, String pattern, String replace) {
int s = 0;
int e = 0;
StringBuffer result = new StringBuffer();
while ((e = str.indexOf(pattern, s)) >= 0) {
result.append(str.substring(s, e));
result.append(replace);
s = e + pattern.length();
}
result.append(str.substring(s));
return result.toString();
}
@Override
public void updateExtraData(String type, String set, Long id,
Map<String, String> queryParams) {
// TODO should be in a Write class
logger.info("updateExtraData - type: " + type + " set: " + set
+ " id: " + id);
checkResourceTypeThere(type, set);
String fullDatasetName = getFullDatasetName(type, set);
String transType = getTransType(type, fullDatasetName, id);
String saveSql = getSaveSql(fullDatasetName, id, transType, queryParams);
logger.info("saveSQL: " + saveSql);
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
db_statement.executeUpdate(saveSql);
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Additional Fields Type: " + type
+ " Set: " + set + " Id: " + id);
} finally {
dbClose(db_statement, db_connection);
}
}
private void checkResourceTypeThere(String type, String set) {
String sql = "select 'f' from stretchydata_datasettype t join stretchydata_dataset d on d.datasettype_id = t.id where d.`name` = '"
+ set + "' and t.`name` = '" + type + "'";
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
ResultSet rs = db_statement.executeQuery(sql);
if (!(rs.next())) {
throw new AdditionalFieldsNotFoundException(type, set);
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Additional Fields Type: " + type
+ " Set: " + set + " Sql: " + sql);
} finally {
dbClose(db_statement, db_connection);
}
}
private String getTransType(String type, String fullDatasetName, Long id) {
String transType = null;
String sql = "select s.id from `" + type + "` t left join `"
+ fullDatasetName + "` s on s.id = t.id where t.id = " + id;
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
ResultSet rs = db_statement.executeQuery(sql);
if (rs.next()) {
String idValue = rs.getString("id");
if (idValue != null) {
transType = "E";
} else {
transType = "A";
}
} else {
throw new AdditionalFieldsNotFoundException(type, id);
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Additional Fields Type: " + type
+ " Full Set Name: " + fullDatasetName
+ " Sql: " + sql);
} finally {
dbClose(db_statement, db_connection);
}
return transType;
}
private String getSaveSql(String fullSetName, Long id, String transType,
Map<String, String> queryParams) {
String pValue = "";
String pValueWrite = "";
String saveSql = "";
String singleQuote = "'";
String underscore = "_";
String space = " ";
Set<String> keys = queryParams.keySet();
if (transType.equals("E")) {
boolean firstColumn = true;
saveSql = "update `" + fullSetName + "` ";
for (String key : keys) {
if (!(key.equalsIgnoreCase("id"))) {
if (firstColumn) {
saveSql += " set ";
firstColumn = false;
} else {
saveSql += ", ";
}
pValue = queryParams.get(key);
if (pValue == null || pValue.equals("")) {
pValueWrite = "null";
} else {
pValueWrite = singleQuote
+ replace(pValue, singleQuote, singleQuote
+ singleQuote) + singleQuote;
}
saveSql += "`" + replace(key, underscore, space) + "` = "
+ pValueWrite;
}
}
saveSql += " where id = " + id;
} else {
String insertColumns = "";
String selectColumns = "";
String columnName = "";
for (String key : keys) {
pValue = queryParams.get(key);
if (!(key.equalsIgnoreCase("id"))) {
pValue = queryParams.get(key);
if (pValue == null || pValue.equals("")) {
pValueWrite = "null";
} else {
pValueWrite = singleQuote
+ replace(pValue, singleQuote, singleQuote
+ singleQuote) + singleQuote;
}
columnName = "`" + replace(key, underscore, space) + "`";
insertColumns += ", " + columnName;
selectColumns += "," + pValueWrite + " as " + columnName;
}
}
saveSql = "insert into `" + fullSetName + "` (id" + insertColumns
+ ")" + " select " + id + " as id" + selectColumns;
}
return saveSql;
}
@Override
public String getReportType(String reportName) {
String sql = "SELECT ifnull(report_type,'') as report_type FROM `stretchy_report` where report_name = '"
+ reportName + "'";
String reportType = "";
logger.info("get reportType: " + sql);
Connection db_connection = null;
Statement db_statement = null;
ResultSet rs = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
rs = db_statement.executeQuery(sql);
if (rs.next()) {
reportType = rs.getString("report_type");
} else {
throw new ReportNotFoundException(sql);
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Report Name: " + reportName + " Sql: "
+ sql);
} finally {
dbClose(db_statement, db_connection);
}
return reportType;
}
private void dbClose(Statement db_statement, Connection db_connection) {
logger.debug("dbClose");
try {
if (db_statement != null) {
db_statement.close();
db_statement = null;
}
if (db_connection != null) {
db_connection.close();
db_connection = null;
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Error closing database connection");
}
}
@Override
public Response processPentahoRequest(String reportName,
String outputTypeParam, Map<String, String> 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 - 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 = "/var/lib/tomcat7/webapps/ROOT/PentahoReports/"
+ reportName + ".prpt";
logger.info("Report path: " + reportPath);
// load report definition
ResourceManager manager = new ResourceManager();
manager.registerDefaults();
Resource res;
logger.info("outputType: " + outputType);
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(MasterReport report,
Map<String, String> queryParams) {
try {
ReportParameterValues rptParamValues = report.getParameterValues();
ReportParameterDefinition paramsDefinition = report
.getParameterDefinition();
/*
* only allow integer 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();
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.sql.Date"))
rptParamValues.put(paramName, Date.valueOf(pValue));
else
rptParamValues.put(paramName, pValue);
}
} catch (Exception e) {
throw new PlatformDataIntegrityException(
"error.msg.reporting.error", e.getMessage());
}
}
}
@@ -23,7 +23,6 @@ import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.mifosng.platform.InvalidSqlException;
import org.mifosng.platform.ReadExtraDataAndReportingService;
import org.mifosng.platform.api.data.AdditionalFieldsSetData;
import org.mifosng.platform.api.data.ApiParameterError;
import org.mifosng.platform.api.data.EntityIdentifier;
@@ -32,6 +31,7 @@ import org.mifosng.platform.api.infrastructure.ApiJsonSerializerService;
import org.mifosng.platform.api.infrastructure.ApiParameterHelper;
import org.mifosng.platform.exceptions.NoAuthorizationException;
import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
import org.mifosng.platform.noncore.ReadWriteNonCoreDataService;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.slf4j.Logger;
@@ -56,7 +56,7 @@ public class AdditionalFieldsApiResource {
}
@Autowired
private ReadExtraDataAndReportingService readExtraDataAndReportingService;
private ReadWriteNonCoreDataService readWriteAdditionalFieldsService;
@Autowired
private ApiJsonSerializerService apiJsonSerializerService;
@@ -67,7 +67,7 @@ public class AdditionalFieldsApiResource {
public String datasets(@QueryParam("type") final String type,
@Context final UriInfo uriInfo) {
List<AdditionalFieldsSetData> result = this.readExtraDataAndReportingService
List<AdditionalFieldsSetData> result = this.readWriteAdditionalFieldsService
.retrieveExtraDatasetNames(type);
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo
@@ -87,7 +87,7 @@ public class AdditionalFieldsApiResource {
checkUserPermissionForSet(type, set, "READ");
try {
GenericResultsetData result = this.readExtraDataAndReportingService
GenericResultsetData result = this.readWriteAdditionalFieldsService
.retrieveExtraData(type, set, id);
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo
@@ -142,7 +142,7 @@ public class AdditionalFieldsApiResource {
.entity("JSON body is wrong").build());
}
this.readExtraDataAndReportingService.updateExtraData(type, set,
this.readWriteAdditionalFieldsService.updateExtraData(type, set,
id, queryParams);
EntityIdentifier entityIdentifier = new EntityIdentifier(
@@ -12,9 +12,9 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.mifosng.platform.InvalidSqlException;
import org.mifosng.platform.ReadExtraDataAndReportingService;
import org.mifosng.platform.api.data.ApiParameterError;
import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
import org.mifosng.platform.noncore.ReadWriteNonCoreDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -24,28 +24,34 @@ import org.springframework.stereotype.Component;
@Scope("singleton")
public class DataTableApiResource {
// private final static Logger logger = LoggerFactory.getLogger(DataTableApiResource.class);
// private final static Logger logger =
// LoggerFactory.getLogger(DataTableApiResource.class);
@Autowired
private ReadExtraDataAndReportingService readExtraDataAndReportingService;
private ReadWriteNonCoreDataService readWriteNonCoreDataService;
@GET
@Path("{datatable}")
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String extraData(@PathParam("datatable") final String datatable,
@QueryParam("sqlFields") final String sqlFields,
@QueryParam("sqlSearch") final String sqlSearch,
@QueryParam("sqlOrder") final String sqlOrder
// ,@Context final UriInfo uriInfo
) {
// ,@Context final UriInfo uriInfo
) {
try {
//GenericResultsetData result = this.readExtraDataAndReportingService.retrieveDataTable(datatable);
return this.readExtraDataAndReportingService.retrieveDataTable(datatable, sqlFields, sqlSearch, sqlOrder);
// GenericResultsetData result =
// this.readExtraDataAndReportingService.retrieveDataTable(datatable);
return this.readWriteNonCoreDataService.retrieveDataTable(
datatable, sqlFields, sqlSearch, sqlOrder);
//boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
//return this.apiDataConversionService.convertGenericResultsetDataToJson(prettyPrint, result);
// boolean prettyPrint =
// ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
// return
// this.apiDataConversionService.convertGenericResultsetDataToJson(prettyPrint,
// result);
} catch (InvalidSqlException e) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
ApiParameterError error = ApiParameterError.parameterError(
@@ -16,11 +16,11 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;
import org.mifosng.platform.ReadExtraDataAndReportingService;
import org.mifosng.platform.api.data.GenericResultsetData;
import org.mifosng.platform.api.infrastructure.ApiJsonSerializerService;
import org.mifosng.platform.api.infrastructure.ApiParameterHelper;
import org.mifosng.platform.exceptions.NoAuthorizationException;
import org.mifosng.platform.noncore.ReadReportingService;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
@@ -40,7 +40,7 @@ public class ReportsApiResource {
}
@Autowired
private ReadExtraDataAndReportingService readExtraDataAndReportingService;
private ReadReportingService readExtraDataAndReportingService;
@Autowired
private ApiJsonSerializerService apiJsonSerializerService;
@@ -0,0 +1,16 @@
package org.mifosng.platform.noncore;
import java.sql.Connection;
import java.sql.Statement;
import org.mifosng.platform.api.data.GenericResultsetData;
public interface GenericDataService {
GenericResultsetData fillGenericResultSet(final String sql);
String replace(String str, String pattern, String replace);
void dbClose(Statement db_statement, Connection db_connection);
}
@@ -0,0 +1,117 @@
package org.mifosng.platform.noncore;
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 org.mifosng.platform.api.data.GenericResultsetData;
import org.mifosng.platform.api.data.ResultsetColumnHeader;
import org.mifosng.platform.api.data.ResultsetDataRow;
import org.mifosng.platform.exceptions.PlatformDataIntegrityException;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class GenericDataServiceImpl implements GenericDataService {
private final static Logger logger = LoggerFactory
.getLogger(GenericDataServiceImpl.class);
private final DataSource dataSource;
@Autowired
public GenericDataServiceImpl(final TenantAwareRoutingDataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public GenericResultsetData fillGenericResultSet(final String sql) {
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
ResultSet rs = db_statement.executeQuery(sql);
List<ResultsetColumnHeader> columnHeaders = new ArrayList<ResultsetColumnHeader>();
List<ResultsetDataRow> resultsetDataRows = new ArrayList<ResultsetDataRow>();
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);
}
ResultsetDataRow resultsetDataRow;
while (rs.next()) {
resultsetDataRow = new ResultsetDataRow();
List<String> columnValues = new ArrayList<String>();
for (int i = 0; i < rsmd.getColumnCount(); i++) {
columnName = rsmd.getColumnName(i + 1);
columnValue = rs.getString(columnName);
columnValues.add(columnValue);
}
resultsetDataRow.setRow(columnValues);
resultsetDataRows.add(resultsetDataRow);
}
return new GenericResultsetData(columnHeaders, resultsetDataRows);
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Sql: " + sql);
} finally {
dbClose(db_statement, db_connection);
}
}
@Override
public String replace(String str, String pattern, String replace) {
// JPW - this replace may / may not be any better or quick that the
// apache stringutils equivalent. It works. but if someone shows the
// apache one to be about the same then this can be removed.
int s = 0;
int e = 0;
StringBuffer result = new StringBuffer();
while ((e = str.indexOf(pattern, s)) >= 0) {
result.append(str.substring(s, e));
result.append(replace);
s = e + pattern.length();
}
result.append(str.substring(s));
return result.toString();
}
@Override
public void dbClose(Statement db_statement, Connection db_connection) {
logger.debug("dbClose");
try {
if (db_statement != null) {
db_statement.close();
db_statement = null;
}
if (db_connection != null) {
db_connection.close();
db_connection = null;
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Error closing database connection");
}
}
}
@@ -1,15 +1,13 @@
package org.mifosng.platform;
package org.mifosng.platform.noncore;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import org.mifosng.platform.api.data.AdditionalFieldsSetData;
import org.mifosng.platform.api.data.GenericResultsetData;
public interface ReadExtraDataAndReportingService {
public interface ReadReportingService {
StreamingOutput retrieveReportCSV(String name, String type,
Map<String, String> extractedQueryParams);
@@ -17,15 +15,6 @@ public interface ReadExtraDataAndReportingService {
GenericResultsetData retrieveGenericResultset(String name, String type,
Map<String, String> extractedQueryParams);
List<AdditionalFieldsSetData> retrieveExtraDatasetNames(String type);
GenericResultsetData retrieveExtraData(String type, String set, Long id);
void updateExtraData(String type, String set, Long id,
Map<String, String> queryParams);
String retrieveDataTable(String datatable, String sqlFields, String sqlSearch, String sqlOrder);
Response processPentahoRequest(String reportName, String outputType,
Map<String, String> queryParams);
@@ -0,0 +1,438 @@
package org.mifosng.platform.noncore;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import org.apache.commons.lang.StringUtils;
import org.mifosng.platform.api.data.GenericResultsetData;
import org.mifosng.platform.api.data.ResultsetColumnHeader;
import org.mifosng.platform.api.data.ResultsetDataRow;
import org.mifosng.platform.exceptions.PlatformDataIntegrityException;
import org.mifosng.platform.exceptions.ReportNotFoundException;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.PdfReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.csv.CSVReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.xls.ExcelReportUtil;
import org.pentaho.reporting.engine.classic.core.parameters.ParameterDefinitionEntry;
import org.pentaho.reporting.engine.classic.core.parameters.ReportParameterDefinition;
import org.pentaho.reporting.engine.classic.core.util.ReportParameterValues;
import org.pentaho.reporting.libraries.resourceloader.Resource;
import org.pentaho.reporting.libraries.resourceloader.ResourceException;
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.stereotype.Service;
@Service
public class ReadReportingServiceImpl implements
ReadReportingService {
private final PlatformSecurityContext context;
private final static Logger logger = LoggerFactory
.getLogger(ReadReportingServiceImpl.class);
private final DataSource dataSource;
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<String, String> queryParams) {
return new StreamingOutput() {
@Override
public void write(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 static StringBuffer generateCsvFileBuffer(
GenericResultsetData result) {
StringBuffer writer = new StringBuffer();
List<ResultsetColumnHeader> 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<ResultsetDataRow> data = result.getData();
List<String> row;
Integer rSize;
// String currCol;
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++) {
// 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('"' + currVal + '"');
}
if (j < (rSize - 1))
writer.append(",");
}
writer.append('\n');
}
return writer;
}
@Override
public GenericResultsetData retrieveGenericResultset(final String name,
final String type, final Map<String, String> 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 (r.name = 'Super User' or r.name = 'Read Only') or p.code = concat('CAN_RUN_', 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<String, String> queryParams) {
String sql = null;
if (type.equals("report")) {
sql = getReportSql(name);
} else {
sql = getParameterSql(name);
}
Set<String> 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());
// wrap sql to prevent JDBC sql errors and also prevent malicious sql
sql = "select x.* from (" + sql + ") x";
return sql;
}
private String getReportSql(String reportName) {
String sql = "select report_sql as the_sql from stretchy_report where report_name = '"
+ reportName + "'";
return getSql(sql);
}
private String getParameterSql(String parameterName) {
String sql = "select parameter_sql as the_sql from stretchy_parameter where parameter_name = '"
+ parameterName + "'";
return getSql(sql);
}
private String getSql(String inputSql) {
String sql = null;
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
ResultSet rs = db_statement.executeQuery(inputSql);
if (rs.next()) {
sql = rs.getString("the_sql");
} else {
throw new ReportNotFoundException(inputSql);
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Input Sql: " + inputSql);
} finally {
genericDataService.dbClose(db_statement, db_connection);
}
return sql;
}
@Override
public String getReportType(String reportName) {
String sql = "SELECT ifnull(report_type,'') as report_type FROM `stretchy_report` where report_name = '"
+ reportName + "'";
String reportType = "";
Connection db_connection = null;
Statement db_statement = null;
ResultSet rs = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
rs = db_statement.executeQuery(sql);
if (rs.next()) {
reportType = rs.getString("report_type");
} else {
throw new ReportNotFoundException(sql);
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Report Name: " + reportName + " Sql: "
+ sql);
} finally {
genericDataService.dbClose(db_statement, db_connection);
}
return reportType;
}
@Override
public Response processPentahoRequest(String reportName,
String outputTypeParam, Map<String, String> 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 - 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 = "/var/lib/tomcat7/webapps/ROOT/PentahoReports/"
+ reportName + ".prpt";
logger.info("Report path: " + reportPath);
// load report definition
ResourceManager manager = new ResourceManager();
manager.registerDefaults();
Resource res;
logger.info("outputType: " + outputType);
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(MasterReport report,
Map<String, String> queryParams) {
try {
ReportParameterValues rptParamValues = report.getParameterValues();
ReportParameterDefinition paramsDefinition = report
.getParameterDefinition();
/*
* only allow integer 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();
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.sql.Date"))
rptParamValues.put(paramName, Date.valueOf(pValue));
else
rptParamValues.put(paramName, pValue);
}
} catch (Exception e) {
throw new PlatformDataIntegrityException(
"error.msg.reporting.error", e.getMessage());
}
}
}
@@ -0,0 +1,20 @@
package org.mifosng.platform.noncore;
import java.util.List;
import java.util.Map;
import org.mifosng.platform.api.data.AdditionalFieldsSetData;
import org.mifosng.platform.api.data.GenericResultsetData;
public interface ReadWriteNonCoreDataService {
List<AdditionalFieldsSetData> retrieveExtraDatasetNames(String type);
GenericResultsetData retrieveExtraData(String type, String set, Long id);
void updateExtraData(String type, String set, Long id,
Map<String, String> queryParams);
String retrieveDataTable(String datatable, String sqlFields, String sqlSearch, String sqlOrder);
}
@@ -0,0 +1,511 @@
package org.mifosng.platform.noncore;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import org.mifosng.platform.api.data.AdditionalFieldsSetData;
import org.mifosng.platform.api.data.GenericResultsetData;
import org.mifosng.platform.api.data.ResultsetColumnHeader;
import org.mifosng.platform.api.data.ResultsetDataRow;
import org.mifosng.platform.exceptions.AdditionalFieldsNotFoundException;
import org.mifosng.platform.exceptions.PlatformDataIntegrityException;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ReadWriteNonCoreDataServiceImpl implements
ReadWriteNonCoreDataService {
private final PlatformSecurityContext context;
private final static Logger logger = LoggerFactory
.getLogger(ReadWriteNonCoreDataServiceImpl.class);
private final DataSource dataSource;
@Autowired
public ReadWriteNonCoreDataServiceImpl(
final PlatformSecurityContext context,
final TenantAwareRoutingDataSource dataSource) {
this.context = context;
this.dataSource = dataSource;
}
@Autowired
private GenericDataService genericDataService;
@Override
public List<AdditionalFieldsSetData> retrieveExtraDatasetNames(String type) {
List<AdditionalFieldsSetData> additionalFieldsSets = new ArrayList<AdditionalFieldsSetData>();
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
String andClause;
if (type == null) {
andClause = "";
} else {
andClause = " and t.`name` = '" + type + "'";
}
String sql = "select d.id, d.`name` as 'set', t.`name` as 'type' "
+ " from stretchydata_dataset d join stretchydata_datasettype t on t.id = d.datasettype_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 (r.name = 'Super User' or r.name = 'Read Only') or p.code = concat('CAN_READ_', t.`name`, '_x', d.`name`)) "
+ andClause + " order by d.`name`";
ResultSet rs = db_statement.executeQuery(sql);
while (rs.next()) {
additionalFieldsSets.add(new AdditionalFieldsSetData(rs
.getInt("id"), rs.getString("set"), rs
.getString("type")));
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Additional Fields Type: " + type);
} finally {
genericDataService.dbClose(db_statement, db_connection);
}
return additionalFieldsSets;
}
@Override
public GenericResultsetData retrieveExtraData(String type, String set,
Long id) {
long startTime = System.currentTimeMillis();
GenericResultsetData result = fillExtraDataGenericResultSet(type, set,
id);
long elapsed = System.currentTimeMillis() - startTime;
logger.info("FINISHING SET: " + set + " Elapsed Time: " + elapsed);
return result;
}
private GenericResultsetData fillExtraDataGenericResultSet(
final String type, final String set, final Long id) {
Connection db_connection = null;
Statement db_statement1 = null;
Statement db_statement2 = null;
Statement db_statement3 = null;
try {
db_connection = dataSource.getConnection();
db_statement1 = db_connection.createStatement();
String sql = "select f.`name`, f.data_type, f.data_length, f.display_type, f.allowed_list_id from stretchydata_datasettype t join stretchydata_dataset d on d.datasettype_id = t.id join stretchydata_dataset_fields f on f.dataset_id = d.id where d.`name` = '"
+ set + "' and t.`name` = '" + type + "' order by f.id";
ResultSet rsmd = db_statement1.executeQuery(sql);
List<ResultsetColumnHeader> columnHeaders = null;
List<ResultsetDataRow> resultsetDataRows = null;
if (rsmd.next()) {
String fullDatasetName = getFullDatasetName(type, set);
db_statement2 = db_connection.createStatement();
columnHeaders = new ArrayList<ResultsetColumnHeader>();
Boolean firstColumn = true;
Integer allowedListId;
String selectFieldList = "";
String selectFieldSeparator = "";
do {
ResultsetColumnHeader rsch = new ResultsetColumnHeader();
rsch.setColumnName(rsmd.getString("name"));
if (firstColumn) {
selectFieldSeparator = " ";
firstColumn = false;
} else {
selectFieldSeparator = ", ";
}
selectFieldList += selectFieldSeparator + "s.`"
+ rsch.getColumnName() + "`";
rsch.setColumnType(rsmd.getString("data_type"));
if (rsmd.getInt("data_length") > 0)
rsch.setColumnLength(rsmd.getInt("data_length"));
rsch.setColumnDisplayType(rsmd.getString("display_type"));
allowedListId = rsmd.getInt("allowed_list_id");
if (allowedListId > 0) {
// logger.info("allowedListId != null: Column: " +
// rsch.getColumnName());
sql = "select v.`name` from stretchydata_allowed_value v where allowed_list_id = "
+ allowedListId + " order by id";
ResultSet rsValues = db_statement2.executeQuery(sql);
while (rsValues.next()) {
rsch.getColumnValues().add(
rsValues.getString("name"));
}
}
columnHeaders.add(rsch);
} while (rsmd.next());
String unScopedSQL = "select " + selectFieldList + " from `"
+ type + "` t ${dataScopeCriteria} left join `"
+ fullDatasetName + "` s on s.id = t.id "
+ " where t.id = " + id;
sql = dataScopedSQL(unScopedSQL, type);
logger.info("addition fields sql: " + sql);
db_statement3 = db_connection.createStatement();
ResultSet rs = db_statement3.executeQuery(sql);
if (rs.next()) {
String columnName = null;
String columnValue = null;
resultsetDataRows = new ArrayList<ResultsetDataRow>();
ResultsetDataRow resultsetDataRow;
do {
resultsetDataRow = new ResultsetDataRow();
List<String> columnValues = new ArrayList<String>();
for (int i = 0; i < columnHeaders.size(); i++) {
columnName = columnHeaders.get(i).getColumnName();
columnValue = rs.getString(columnName);
columnValues.add(columnValue);
}
resultsetDataRow.setRow(columnValues);
resultsetDataRows.add(resultsetDataRow);
} while (rs.next());
} else {
// could also be not found because of data scope
throw new AdditionalFieldsNotFoundException(type, id);
}
} else {
throw new AdditionalFieldsNotFoundException(type, set);
}
return new GenericResultsetData(columnHeaders, resultsetDataRows);
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Additional Fields Type: " + type
+ " Set: " + set + " Id: " + id);
} finally {
genericDataService.dbClose(db_statement2, null);
genericDataService.dbClose(db_statement3, null);
genericDataService.dbClose(db_statement1, db_connection);
}
}
@Override
public void updateExtraData(String type, String set, Long id,
Map<String, String> queryParams) {
logger.info("updateExtraData - type: " + type + " set: " + set
+ " id: " + id);
checkResourceTypeThere(type, set);
String fullDatasetName = getFullDatasetName(type, set);
String transType = getTransType(type, fullDatasetName, id);
String saveSql = getSaveSql(fullDatasetName, id, transType, queryParams);
logger.info("saveSQL: " + saveSql);
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
db_statement.executeUpdate(saveSql);
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Additional Fields Type: " + type
+ " Set: " + set + " Id: " + id);
} finally {
genericDataService.dbClose(db_statement, db_connection);
}
}
private void checkResourceTypeThere(String type, String set) {
String sql = "select 'f' from stretchydata_datasettype t join stretchydata_dataset d on d.datasettype_id = t.id where d.`name` = '"
+ set + "' and t.`name` = '" + type + "'";
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
ResultSet rs = db_statement.executeQuery(sql);
if (!(rs.next())) {
throw new AdditionalFieldsNotFoundException(type, set);
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Additional Fields Type: " + type
+ " Set: " + set + " Sql: " + sql);
} finally {
genericDataService.dbClose(db_statement, db_connection);
}
}
private String getTransType(String type, String fullDatasetName, Long id) {
String transType = null;
String sql = "select s.id from `" + type + "` t left join `"
+ fullDatasetName + "` s on s.id = t.id where t.id = " + id;
Connection db_connection = null;
Statement db_statement = null;
try {
db_connection = dataSource.getConnection();
db_statement = db_connection.createStatement();
ResultSet rs = db_statement.executeQuery(sql);
if (rs.next()) {
String idValue = rs.getString("id");
if (idValue != null) {
transType = "E";
} else {
transType = "A";
}
} else {
throw new AdditionalFieldsNotFoundException(type, id);
}
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error",
e.getMessage(), "Additional Fields Type: " + type
+ " Full Set Name: " + fullDatasetName
+ " Sql: " + sql);
} finally {
genericDataService.dbClose(db_statement, db_connection);
}
return transType;
}
private String getSaveSql(String fullSetName, Long id, String transType,
Map<String, String> queryParams) {
String pValue = "";
String pValueWrite = "";
String saveSql = "";
String singleQuote = "'";
String underscore = "_";
String space = " ";
Set<String> keys = queryParams.keySet();
if (transType.equals("E")) {
boolean firstColumn = true;
saveSql = "update `" + fullSetName + "` ";
for (String key : keys) {
if (!(key.equalsIgnoreCase("id"))) {
if (firstColumn) {
saveSql += " set ";
firstColumn = false;
} else {
saveSql += ", ";
}
pValue = queryParams.get(key);
if (pValue == null || pValue.equals("")) {
pValueWrite = "null";
} else {
pValueWrite = singleQuote
+ genericDataService.replace(pValue,
singleQuote, singleQuote + singleQuote)
+ singleQuote;
}
saveSql += "`"
+ genericDataService
.replace(key, underscore, space) + "` = "
+ pValueWrite;
}
}
saveSql += " where id = " + id;
} else {
String insertColumns = "";
String selectColumns = "";
String columnName = "";
for (String key : keys) {
pValue = queryParams.get(key);
if (!(key.equalsIgnoreCase("id"))) {
pValue = queryParams.get(key);
if (pValue == null || pValue.equals("")) {
pValueWrite = "null";
} else {
pValueWrite = singleQuote
+ genericDataService.replace(pValue,
singleQuote, singleQuote + singleQuote)
+ singleQuote;
}
columnName = "`"
+ genericDataService
.replace(key, underscore, space) + "`";
insertColumns += ", " + columnName;
selectColumns += "," + pValueWrite + " as " + columnName;
}
}
saveSql = "insert into `" + fullSetName + "` (id" + insertColumns
+ ")" + " select " + id + " as id" + selectColumns;
}
return saveSql;
}
private String dataScopedSQL(String unScopedSQL, String type) {
String dataScopeCriteria = null;
/*
* unfortunately have to, one way or another, be able to restrict data
* to the users office hierarchy. Here it's hardcoded for client and
* loan. They are the main application tables. But if additional fields
* are needed on other tables like group, loan_transaction or others the
* same applies (hardcoding of some sort)
*/
AppUser currentUser = context.authenticatedUser();
if (type.equalsIgnoreCase("m_client")) {
dataScopeCriteria = " join m_office o on o.id = t.office_id and o.hierarchy like '"
+ currentUser.getOffice().getHierarchy() + "%'";
}
if (type.equalsIgnoreCase("m_loan")) {
dataScopeCriteria = " join m_client c on c.id = t.client_id "
+ " join m_office o on o.id = c.office_id and o.hierarchy like '"
+ currentUser.getOffice().getHierarchy() + "%'";
}
if (dataScopeCriteria == null) {
throw new PlatformDataIntegrityException(
"error.msg.invalid.dataScopeCriteria", "Type: " + type
+ " not catered for in data Scoping");
}
return genericDataService.replace(unScopedSQL, "${dataScopeCriteria}",
dataScopeCriteria);
}
private String getFullDatasetName(final String type, final String set) {
return type + "_x" + set;
}
@Override
public String retrieveDataTable(String datatable, String sqlFields,
String sqlSearch, String sqlOrder) {
long startTime = System.currentTimeMillis();
String sql = "select ";
if (sqlFields != null)
sql = sql + sqlFields;
else
sql = sql + " * ";
sql = sql + " from " + datatable;
if (sqlSearch != null)
sql = sql + " where " + sqlSearch;
if (sqlOrder != null)
sql = sql + " order by " + sqlOrder;
GenericResultsetData result = genericDataService
.fillGenericResultSet(sql);
String jsonString = generateJsonFromGenericResultsetData(result);
logger.info("JSON is: " + jsonString);
long elapsed = System.currentTimeMillis() - startTime;
logger.info("FINISHING DATATABLE: " + datatable + " Elapsed Time: "
+ elapsed);
return jsonString;
}
private static String generateJsonFromGenericResultsetData(
GenericResultsetData result) {
StringBuffer writer = new StringBuffer();
writer.append("[");
List<ResultsetColumnHeader> columnHeaders = result.getColumnHeaders();
logger.info("NO. of Columns: " + columnHeaders.size());
List<ResultsetDataRow> data = result.getData();
List<String> row;
Integer rSize;
String currColType;
String currVal;
logger.info("NO. of Rows: " + data.size());
for (int i = 0; i < data.size(); i++) {
writer.append("\n{");
row = data.get(i).getRow();
rSize = row.size();
for (int j = 0; j < rSize; j++) {
writer.append('\"' + 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('\"' + currVal + '\"');
} else
writer.append("null");
if (j < (rSize - 1))
writer.append(",\n");
}
if (i < (data.size() - 1))
writer.append("},");
else
writer.append("}");
}
writer.append("\n]");
return writer.toString();
/*
* JSONObject js = null; try { js = new JSONObject(writer.toString()); }
* catch (JSONException e) { throw new WebApplicationException(Response
* .status(Status.BAD_REQUEST).entity("JSON body is wrong") .build()); }
*
* return js.toString();
*/
}
}