From df944f44547936edde85d67f65fcd7092af9ce7a Mon Sep 17 00:00:00 2001 From: Karthikeyan Rajendran <70887864+karthik-tarento@users.noreply.github.com> Date: Tue, 20 Dec 2022 18:20:54 +0530 Subject: [PATCH] 4.0.1 user report (#163) * Added user and user enrolment report generation feature Co-authored-by: Manas-tarento Co-authored-by: Juhi --- pom.xml | 12 +- .../cassandra/utils/CassandraOperation.java | 132 ++--- .../utils/CassandraOperationImpl.java | 495 ++++++++++-------- .../common/model/SearchUserApiContent.java | 49 +- .../common/service/ContentService.java | 19 +- .../common/service/ContentServiceImpl.java | 89 +++- .../org/sunbird/common/util/Constants.java | 27 + .../org/sunbird/common/util/ProjectUtil.java | 142 +++-- .../sunbird/core/cipher/BASE64Decoder.java | 104 ++++ .../sunbird/core/cipher/CharacterDecoder.java | 140 +++++ .../core/cipher/DecryptServiceImpl.java | 62 +++ .../sunbird/core/cipher/SecretKeySpec.java | 65 +++ .../org/service/ExtendedOrgService.java | 3 + .../org/service/ExtendedOrgServiceImpl.java | 58 ++ .../profile/controller/ProfileController.java | 12 + .../profile/service/ProfileService.java | 4 + .../profile/service/ProfileServiceImpl.java | 300 ++++++++++- .../user/report/UserReportService.java | 15 + .../user/report/UserReportServiceImpl.java | 132 +++++ .../user/service/UserUtilityService.java | 9 + .../user/service/UserUtilityServiceImpl.java | 154 +++++- src/main/resources/application.properties | 5 + 22 files changed, 1632 insertions(+), 396 deletions(-) create mode 100644 src/main/java/org/sunbird/core/cipher/BASE64Decoder.java create mode 100644 src/main/java/org/sunbird/core/cipher/CharacterDecoder.java create mode 100644 src/main/java/org/sunbird/core/cipher/DecryptServiceImpl.java create mode 100644 src/main/java/org/sunbird/core/cipher/SecretKeySpec.java create mode 100644 src/main/java/org/sunbird/user/report/UserReportService.java create mode 100644 src/main/java/org/sunbird/user/report/UserReportServiceImpl.java diff --git a/pom.xml b/pom.xml index a7a46a47b..93d43dec3 100644 --- a/pom.xml +++ b/pom.xml @@ -160,7 +160,17 @@ 18.0.0 - + + + org.apache.poi + poi + 3.11 + + + org.apache.poi + poi-ooxml + 3.11 + diff --git a/src/main/java/org/sunbird/cassandra/utils/CassandraOperation.java b/src/main/java/org/sunbird/cassandra/utils/CassandraOperation.java index 198a5729f..b40f56494 100644 --- a/src/main/java/org/sunbird/cassandra/utils/CassandraOperation.java +++ b/src/main/java/org/sunbird/cassandra/utils/CassandraOperation.java @@ -12,77 +12,79 @@ */ public interface CassandraOperation { - /** - * @param keyspaceName Keyspace name - * @param tableName Table name - * @param request Map(i.e map of column name and their value) - * @return Response - * @desc This method is used to insert record in cassandra db - */ + /** + * @param keyspaceName Keyspace name + * @param tableName Table name + * @param request Map(i.e map of column name and their + * value) + * @return Response + * @desc This method is used to insert record in cassandra db + */ - public SBApiResponse insertRecord(String keyspaceName, String tableName, Map request); + public SBApiResponse insertRecord(String keyspaceName, String tableName, Map request); - /** - * Insert bulk data using batch - * - * @param keyspaceName String - * @param tableName String - * @param request List> - * @return SBApiResponse - */ - public SBApiResponse insertBulkRecord(String keyspaceName, String tableName, List> request); + /** + * Insert bulk data using batch + * + * @param keyspaceName String + * @param tableName String + * @param request List> + * @return SBApiResponse + */ + public SBApiResponse insertBulkRecord(String keyspaceName, String tableName, List> request); - /** - * Fetch records with specified columns (select all if null) for given column - * map (name, value pairs). - * - * @param keyspaceName Keyspace name - * @param tableName Table name - * @param propertyMap Map describing columns to be used in where clause of select query. - * @param fields List of columns to be returned in each record - * @return List consisting of fetched records - */ - List> getRecordsByProperties(String keyspaceName, String tableName, - Map propertyMap, List fields); + /** + * Fetch records with specified columns (select all if null) for given column + * map (name, value pairs). + * + * @param keyspaceName Keyspace name + * @param tableName Table name + * @param propertyMap Map describing columns to be used in where clause of + * select query. + * @param fields List of columns to be returned in each record + * @return List consisting of fetched records + */ + List> getRecordsByProperties(String keyspaceName, String tableName, + Map propertyMap, List fields); - /** - * @param keyspaceName Keyspace name - * @param tableName Table name - * @param keyMap Column map for composite primary key - * @desc This method is used to delete record in cassandra db by their primary - * composite key - */ - public void deleteRecord(String keyspaceName, String tableName, Map keyMap); + /** + * @param keyspaceName Keyspace name + * @param tableName Table name + * @param keyMap Column map for composite primary key + * @desc This method is used to delete record in cassandra db by their primary + * composite key + */ + public void deleteRecord(String keyspaceName, String tableName, Map keyMap); - /** - * Method to update the record on basis of composite primary key. - * - * @param keyspaceName Keyspace name - * @param tableName Table name - * @param updateAttributes Column map to be used in set clause of update query - * @param compositeKey Column map for composite primary key - * @return Response consisting of update query status - */ - Map updateRecord(String keyspaceName, String tableName, Map updateAttributes, - Map compositeKey); + /** + * Method to update the record on basis of composite primary key. + * + * @param keyspaceName Keyspace name + * @param tableName Table name + * @param updateAttributes Column map to be used in set clause of update query + * @param compositeKey Column map for composite primary key + * @return Response consisting of update query status + */ + Map updateRecord(String keyspaceName, String tableName, Map updateAttributes, + Map compositeKey); - /** - * To get count of all records - * - * @param keyspace String - * @param table String - * @return Long - */ - public Long getRecordCount(String keyspace, String table); + /** + * To get count of all records + * + * @param keyspace String + * @param table String + * @return Long + */ + public Long getRecordCount(String keyspace, String table); - public Map getRecordsByProperties(String keyspaceName, String tableName, - Map propertyMap, List fields,String key); - public Map getRecordsByPropertiesWithPagination(String keyspaceName, String tableName, - Map propertyMap, List fields, int limit, String updatedOn,String key); - List> searchByWhereClause( - String keyspace, - String tableName, - List fields, - Date date); + public Map getRecordsByProperties(String keyspaceName, String tableName, + Map propertyMap, List fields, String key); + public Map getRecordsByPropertiesWithPagination(String keyspaceName, String tableName, + Map propertyMap, List fields, int limit, String updatedOn, String key); + + List> searchByWhereClause(String keyspace, String tableName, List fields, Date date); + + public void getAllRecords(String keyspace, String table, List fields, String key, + Map> objectInfo); } diff --git a/src/main/java/org/sunbird/cassandra/utils/CassandraOperationImpl.java b/src/main/java/org/sunbird/cassandra/utils/CassandraOperationImpl.java index 6f3c2f2ed..fdcde43e9 100644 --- a/src/main/java/org/sunbird/cassandra/utils/CassandraOperationImpl.java +++ b/src/main/java/org/sunbird/cassandra/utils/CassandraOperationImpl.java @@ -18,229 +18,284 @@ import java.util.*; import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; @Component public class CassandraOperationImpl implements CassandraOperation { - private Logger logger = LoggerFactory.getLogger(getClass().getName()); - protected CassandraConnectionManager connectionManager = CassandraConnectionMngrFactory.getInstance(); - - @Override - public SBApiResponse insertRecord(String keyspaceName, String tableName, Map request) { - SBApiResponse response = new SBApiResponse(); - String query = CassandraUtil.getPreparedStatement(keyspaceName, tableName, request); - try { - PreparedStatement statement = connectionManager.getSession(keyspaceName).prepare(query); - BoundStatement boundStatement = new BoundStatement(statement); - Iterator iterator = request.values().iterator(); - Object[] array = new Object[request.keySet().size()]; - int i = 0; - while (iterator.hasNext()) { - array[i++] = iterator.next(); - } - connectionManager.getSession(keyspaceName).execute(boundStatement.bind(array)); - response.put(Constants.RESPONSE, Constants.SUCCESS); - } catch (Exception e) { - logger.error( - String.format("Exception occurred while inserting record to %s %s", tableName, e.getMessage())); - } - return response; - } - - @Override - public SBApiResponse insertBulkRecord(String keyspaceName, String tableName, List> request) { - SBApiResponse response = new SBApiResponse(); - try { - BatchStatement batchStatement = new BatchStatement(); - for (Map requestMap : request) { - String query = CassandraUtil.getPreparedStatement(keyspaceName, tableName, requestMap); - PreparedStatement statement = connectionManager.getSession(keyspaceName).prepare(query); - BoundStatement boundStatement = new BoundStatement(statement); - Iterator iterator = requestMap.values().iterator(); - Object[] array = new Object[requestMap.size()]; - int i = 0; - while (iterator.hasNext()) { - array[i++] = iterator.next(); - } - boundStatement.bind(array); - batchStatement.add(boundStatement); - } - connectionManager.getSession(keyspaceName).execute(batchStatement); - response.put(Constants.RESPONSE, Constants.SUCCESS); - } catch (Exception e) { - logger.error(String.format("Exception occurred while inserting bulk record to %s %s", tableName, - e.getMessage())); - } - return response; - } - - @Override - public List> getRecordsByProperties(String keyspaceName, String tableName, - Map propertyMap, List fields) { - Select selectQuery = null; - List> response = new ArrayList<>(); - try { - selectQuery = processQuery(keyspaceName, tableName, propertyMap, fields); - ResultSet results = connectionManager.getSession(keyspaceName).execute(selectQuery); - response = CassandraUtil.createResponse(results); - - } catch (Exception e) { - logger.error(Constants.EXCEPTION_MSG_FETCH + tableName + " : " + e.getMessage(), e); - } - return response; - } - @Override - public Map getRecordsByProperties(String keyspaceName, String tableName, - Map propertyMap, List fields,String key) { - Select selectQuery = null; - Map response = new HashMap<>(); - try { - selectQuery = processQuery(keyspaceName, tableName, propertyMap, fields); - ResultSet results = connectionManager.getSession(keyspaceName).execute(selectQuery); - response = CassandraUtil.createResponse(results,key); - - } catch (Exception e) { - logger.error(Constants.EXCEPTION_MSG_FETCH + tableName + " : " + e.getMessage(), e); - } - return response; - } - - @Override - public List> searchByWhereClause( - String keyspace, - String tableName, - List fields, - Date date) { - Builder selectBuilder; - if (CollectionUtils.isNotEmpty(fields)) { - String[] dbFields = fields.toArray(new String[fields.size()]); - selectBuilder = QueryBuilder.select(dbFields); - } else { - selectBuilder = QueryBuilder.select().all(); - } - Select selectQuery = selectBuilder.from(keyspace, tableName); - Where selectWhere = selectQuery.where(); - Clause completionpercentagegreaterthanzero = QueryBuilder.gt("completionpercentage", 0); - selectWhere.and(completionpercentagegreaterthanzero); - Clause completionpercentagelessthanhundred= QueryBuilder.lt("completionpercentage", 100); - selectWhere.and(completionpercentagelessthanhundred); - Clause lastAccessTimeNotNull= QueryBuilder.gt("last_access_time", 0); - selectWhere.and(lastAccessTimeNotNull); - selectQuery.allowFiltering(); - Clause lastAccessTime= QueryBuilder.lt("last_access_time", date); - selectWhere.and(lastAccessTime); - logger.debug("our query: "+selectQuery.getQueryString()); - ResultSet resultSet = connectionManager.getSession(keyspace).execute(selectQuery); - return CassandraUtil.createResponse(resultSet); - } - - @Override - public Map getRecordsByPropertiesWithPagination(String keyspaceName, String tableName, - Map propertyMap, List fields, int limit, String updatedOn,String key) { - Select selectQuery = null; - Map response = new HashMap<>(); - try { - selectQuery = processQuery(keyspaceName, tableName, propertyMap, fields); - selectQuery.limit(limit); - if (!StringUtils.isEmpty(updatedOn)) { - selectQuery.where(QueryBuilder.lt("updatedon", UUID.fromString(updatedOn))); - } - ResultSet results = connectionManager.getSession(keyspaceName).execute(selectQuery); - response = CassandraUtil.createResponse(results,key); - } catch (Exception e) { - logger.error(Constants.EXCEPTION_MSG_FETCH + tableName + " : " + e.getMessage(), e); - } - return response; - } - - private Select processQuery(String keyspaceName, String tableName, - Map propertyMap, List fields) { - Select selectQuery = null; - - Builder selectBuilder; - if (CollectionUtils.isNotEmpty(fields)) { - String[] dbFields = fields.toArray(new String[fields.size()]); - selectBuilder = QueryBuilder.select(dbFields); - } else { - selectBuilder = QueryBuilder.select().all(); - } - selectQuery = selectBuilder.from(keyspaceName, tableName); - if (MapUtils.isNotEmpty(propertyMap)) { - Where selectWhere = selectQuery.where(); - for (Entry entry : propertyMap.entrySet()) { - if (entry.getValue() instanceof List) { - List list = (List) entry.getValue(); - if (null != list) { - Object[] propertyValues = list.toArray(new Object[list.size()]); - Clause clause = QueryBuilder.in(entry.getKey(), propertyValues); - selectWhere.and(clause); - - } - } else { - - Clause clause = QueryBuilder.eq(entry.getKey(), entry.getValue()); - selectWhere.and(clause); - - } - selectQuery.allowFiltering(); - } - } - return selectQuery; - } - - @Override - public void deleteRecord(String keyspaceName, String tableName, Map compositeKeyMap) { - Delete delete = null; - try { - delete = QueryBuilder.delete().from(keyspaceName, tableName); - Delete.Where deleteWhere = delete.where(); - compositeKeyMap.entrySet().stream().forEach(x -> { - Clause clause = QueryBuilder.eq(x.getKey(), x.getValue()); - deleteWhere.and(clause); - }); - connectionManager.getSession(keyspaceName).execute(delete); - } catch (Exception e) { - logger.error(String.format("CassandraOperationImpl: deleteRecord by composite key. %s %s %s", - Constants.EXCEPTION_MSG_DELETE, tableName, e.getMessage())); - throw e; - } - } - - @Override - public Map updateRecord(String keyspaceName, String tableName, Map updateAttributes, - Map compositeKey) { - Map response = new HashMap<>(); - Statement updateQuery = null; - try { - Session session = connectionManager.getSession(keyspaceName); - Update update = QueryBuilder.update(keyspaceName, tableName); - Assignments assignments = update.with(); - Update.Where where = update.where(); - updateAttributes.entrySet().stream().forEach(x -> { - assignments.and(QueryBuilder.set(x.getKey(), x.getValue())); - }); - compositeKey.entrySet().stream().forEach(x -> { - where.and(QueryBuilder.eq(x.getKey(), x.getValue())); - }); - updateQuery = where; - session.execute(updateQuery); - response.put(Constants.RESPONSE, Constants.SUCCESS); - } catch (Exception e) { - logger.error(e.getMessage()); - throw e; - } - return response; - } - - @Override - public Long getRecordCount(String keyspace, String table) { - try { - Select selectQuery = QueryBuilder.select().countAll().from(keyspace, table); - Row row = connectionManager.getSession(keyspace).execute(selectQuery).one(); - return row.getLong(0); - } catch (Exception e) { - throw e; - } - } + private Logger logger = LoggerFactory.getLogger(getClass().getName()); + protected CassandraConnectionManager connectionManager = CassandraConnectionMngrFactory.getInstance(); + @Override + public SBApiResponse insertRecord(String keyspaceName, String tableName, Map request) { + SBApiResponse response = new SBApiResponse(); + String query = CassandraUtil.getPreparedStatement(keyspaceName, tableName, request); + try { + PreparedStatement statement = connectionManager.getSession(keyspaceName).prepare(query); + BoundStatement boundStatement = new BoundStatement(statement); + Iterator iterator = request.values().iterator(); + Object[] array = new Object[request.keySet().size()]; + int i = 0; + while (iterator.hasNext()) { + array[i++] = iterator.next(); + } + connectionManager.getSession(keyspaceName).execute(boundStatement.bind(array)); + response.put(Constants.RESPONSE, Constants.SUCCESS); + } catch (Exception e) { + logger.error( + String.format("Exception occurred while inserting record to %s %s", tableName, e.getMessage())); + } + return response; + } + + @Override + public SBApiResponse insertBulkRecord(String keyspaceName, String tableName, List> request) { + SBApiResponse response = new SBApiResponse(); + try { + BatchStatement batchStatement = new BatchStatement(); + for (Map requestMap : request) { + String query = CassandraUtil.getPreparedStatement(keyspaceName, tableName, requestMap); + PreparedStatement statement = connectionManager.getSession(keyspaceName).prepare(query); + BoundStatement boundStatement = new BoundStatement(statement); + Iterator iterator = requestMap.values().iterator(); + Object[] array = new Object[requestMap.size()]; + int i = 0; + while (iterator.hasNext()) { + array[i++] = iterator.next(); + } + boundStatement.bind(array); + batchStatement.add(boundStatement); + } + connectionManager.getSession(keyspaceName).execute(batchStatement); + response.put(Constants.RESPONSE, Constants.SUCCESS); + } catch (Exception e) { + logger.error(String.format("Exception occurred while inserting bulk record to %s %s", tableName, + e.getMessage())); + } + return response; + } + + @Override + public List> getRecordsByProperties(String keyspaceName, String tableName, + Map propertyMap, List fields) { + Select selectQuery = null; + List> response = new ArrayList<>(); + try { + selectQuery = processQuery(keyspaceName, tableName, propertyMap, fields); + ResultSet results = connectionManager.getSession(keyspaceName).execute(selectQuery); + response = CassandraUtil.createResponse(results); + + } catch (Exception e) { + logger.error(Constants.EXCEPTION_MSG_FETCH + tableName + " : " + e.getMessage(), e); + } + return response; + } + + @Override + public Map getRecordsByProperties(String keyspaceName, String tableName, + Map propertyMap, List fields, String key) { + Select selectQuery = null; + Map response = new HashMap<>(); + try { + selectQuery = processQuery(keyspaceName, tableName, propertyMap, fields); + ResultSet results = connectionManager.getSession(keyspaceName).execute(selectQuery); + response = CassandraUtil.createResponse(results, key); + + } catch (Exception e) { + logger.error(Constants.EXCEPTION_MSG_FETCH + tableName + " : " + e.getMessage(), e); + } + return response; + } + + @Override + public List> searchByWhereClause(String keyspace, String tableName, List fields, + Date date) { + Builder selectBuilder; + if (CollectionUtils.isNotEmpty(fields)) { + String[] dbFields = fields.toArray(new String[fields.size()]); + selectBuilder = QueryBuilder.select(dbFields); + } else { + selectBuilder = QueryBuilder.select().all(); + } + Select selectQuery = selectBuilder.from(keyspace, tableName); + Where selectWhere = selectQuery.where(); + Clause completionpercentagegreaterthanzero = QueryBuilder.gt("completionpercentage", 0); + selectWhere.and(completionpercentagegreaterthanzero); + Clause completionpercentagelessthanhundred = QueryBuilder.lt("completionpercentage", 100); + selectWhere.and(completionpercentagelessthanhundred); + Clause lastAccessTimeNotNull = QueryBuilder.gt("last_access_time", 0); + selectWhere.and(lastAccessTimeNotNull); + selectQuery.allowFiltering(); + Clause lastAccessTime = QueryBuilder.lt("last_access_time", date); + selectWhere.and(lastAccessTime); + logger.debug("our query: " + selectQuery.getQueryString()); + ResultSet resultSet = connectionManager.getSession(keyspace).execute(selectQuery); + return CassandraUtil.createResponse(resultSet); + } + + @Override + public Map getRecordsByPropertiesWithPagination(String keyspaceName, String tableName, + Map propertyMap, List fields, int limit, String updatedOn, String key) { + Select selectQuery = null; + Map response = new HashMap<>(); + try { + selectQuery = processQuery(keyspaceName, tableName, propertyMap, fields); + selectQuery.limit(limit); + if (!StringUtils.isEmpty(updatedOn)) { + selectQuery.where(QueryBuilder.lt("updatedon", UUID.fromString(updatedOn))); + } + ResultSet results = connectionManager.getSession(keyspaceName).execute(selectQuery); + response = CassandraUtil.createResponse(results, key); + } catch (Exception e) { + logger.error(Constants.EXCEPTION_MSG_FETCH + tableName + " : " + e.getMessage(), e); + } + return response; + } + + private Select processQuery(String keyspaceName, String tableName, Map propertyMap, + List fields) { + Select selectQuery = null; + + Builder selectBuilder; + if (CollectionUtils.isNotEmpty(fields)) { + String[] dbFields = fields.toArray(new String[fields.size()]); + selectBuilder = QueryBuilder.select(dbFields); + } else { + selectBuilder = QueryBuilder.select().all(); + } + selectQuery = selectBuilder.from(keyspaceName, tableName); + if (MapUtils.isNotEmpty(propertyMap)) { + Where selectWhere = selectQuery.where(); + for (Entry entry : propertyMap.entrySet()) { + if (entry.getValue() instanceof List) { + List list = (List) entry.getValue(); + if (null != list) { + Object[] propertyValues = list.toArray(new Object[list.size()]); + Clause clause = QueryBuilder.in(entry.getKey(), propertyValues); + selectWhere.and(clause); + + } + } else { + + Clause clause = QueryBuilder.eq(entry.getKey(), entry.getValue()); + selectWhere.and(clause); + + } + selectQuery.allowFiltering(); + } + } + return selectQuery; + } + + @Override + public void deleteRecord(String keyspaceName, String tableName, Map compositeKeyMap) { + Delete delete = null; + try { + delete = QueryBuilder.delete().from(keyspaceName, tableName); + Delete.Where deleteWhere = delete.where(); + compositeKeyMap.entrySet().stream().forEach(x -> { + Clause clause = QueryBuilder.eq(x.getKey(), x.getValue()); + deleteWhere.and(clause); + }); + connectionManager.getSession(keyspaceName).execute(delete); + } catch (Exception e) { + logger.error(String.format("CassandraOperationImpl: deleteRecord by composite key. %s %s %s", + Constants.EXCEPTION_MSG_DELETE, tableName, e.getMessage())); + throw e; + } + } + + @Override + public Map updateRecord(String keyspaceName, String tableName, Map updateAttributes, + Map compositeKey) { + Map response = new HashMap<>(); + Statement updateQuery = null; + try { + Session session = connectionManager.getSession(keyspaceName); + Update update = QueryBuilder.update(keyspaceName, tableName); + Assignments assignments = update.with(); + Update.Where where = update.where(); + updateAttributes.entrySet().stream().forEach(x -> { + assignments.and(QueryBuilder.set(x.getKey(), x.getValue())); + }); + compositeKey.entrySet().stream().forEach(x -> { + where.and(QueryBuilder.eq(x.getKey(), x.getValue())); + }); + updateQuery = where; + session.execute(updateQuery); + response.put(Constants.RESPONSE, Constants.SUCCESS); + } catch (Exception e) { + logger.error(e.getMessage()); + throw e; + } + return response; + } + + @Override + public Long getRecordCount(String keyspace, String table) { + try { + Select selectQuery = QueryBuilder.select().countAll().from(keyspace, table); + Row row = connectionManager.getSession(keyspace).execute(selectQuery).one(); + return row.getLong(0); + } catch (Exception e) { + throw e; + } + } + + public void getAllRecords(String keyspace, String table, List fields, String key, + Map> objectInfo) { + Select selectQuery = null; + try { + selectQuery = processQuery(keyspace, table, MapUtils.EMPTY_MAP, fields); + ResultSet results = connectionManager.getSession(keyspace).execute(selectQuery); + Map columnsMapping = CassandraUtil.fetchColumnsMapping(results); + Iterator rowIterator = results.iterator(); + rowIterator.forEachRemaining(row -> { + Map rowMap = new HashMap<>(); + columnsMapping.entrySet().stream().forEach(entry -> { + rowMap.put(entry.getKey(), (String) row.getObject(entry.getValue())); + }); + + objectInfo.put((String) rowMap.get(key), rowMap); + }); + } catch (Exception e) { + logger.error(Constants.EXCEPTION_MSG_FETCH + table + " : " + e.getMessage(), e); + } + } + + public void getAllRecordsWithPagination(String keyspace, String table, List fields, String key, + Map> objectInfo) { + long startTime = System.currentTimeMillis(); + Select selectQuery = processQuery(keyspace, table, MapUtils.EMPTY_MAP, fields); + + int n = 0; + PagingState pageStates = null; + Map stringMap = new HashMap(); + do { + Statement select = selectQuery.setFetchSize(100).setPagingState(pageStates); + ResultSet resultSet = connectionManager.getSession(keyspace).execute(select); + pageStates = resultSet.getExecutionInfo().getPagingState(); + stringMap.put(++n, pageStates); + } while (pageStates != null); + + Iterator> pageIterator = stringMap.entrySet().iterator(); + while (pageIterator.hasNext()) { + Entry pageEntry = pageIterator.next(); + Statement selectq = selectQuery.setPagingState(pageEntry.getValue()); + ResultSet resultSet = connectionManager.getSession(keyspace).execute(selectq); + Map columnsMapping = CassandraUtil.fetchColumnsMapping(resultSet); + + Iterator rowIterator = resultSet.iterator(); + rowIterator.forEachRemaining(row -> { + Map rowMap = new HashMap<>(); + columnsMapping.entrySet().stream().forEach(entry -> { + rowMap.put(entry.getKey(), (String) row.getObject(entry.getValue())); + }); + + objectInfo.put((String) rowMap.get(key), rowMap); + }); + } + logger.info(String.format("Competed Oeration in %s seconds", + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - startTime))); + } } diff --git a/src/main/java/org/sunbird/common/model/SearchUserApiContent.java b/src/main/java/org/sunbird/common/model/SearchUserApiContent.java index 6ab091f06..70bfc007f 100644 --- a/src/main/java/org/sunbird/common/model/SearchUserApiContent.java +++ b/src/main/java/org/sunbird/common/model/SearchUserApiContent.java @@ -2,6 +2,9 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.List; +import java.util.Map; + @JsonIgnoreProperties(ignoreUnknown = true) public class SearchUserApiContent { @@ -12,8 +15,47 @@ public class SearchUserApiContent { private String lastName; private String desc; private String channel; + private String phone; + + private String courseId; + private String rootOrgId; private SunbirdUserProfileDetail profileDetails; + + + public String getCourseId() { + return courseId; + } + + public void setCourseId(String courseId) { + this.courseId = courseId; + } + + private List> organisations = null; + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public List> getOrganisations() { + return organisations; + } + + public void setOrganisations(List> organisations) { + this.organisations = organisations; + } + + public String getRootOrgId() { + return rootOrgId; + } + + public void setRootOrgId(String rootOrgId) { + this.rootOrgId = rootOrgId; + } public String getId() { return id; } @@ -78,7 +120,7 @@ public void setProfileDetails(SunbirdUserProfileDetail profileDetails) { this.profileDetails = profileDetails; } - @Override + @Override public String toString() { return "SearchUserApiContent{" + "id='" + id + '\'' + @@ -87,6 +129,11 @@ public String toString() { ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", desc='" + desc + '\'' + + ", channel='" + channel + '\'' + + ", phone='" + phone + '\'' + + ", rootOrgId='" + rootOrgId + '\'' + + ", profileDetails=" + profileDetails + + ", organisations=" + organisations + '}'; } } diff --git a/src/main/java/org/sunbird/common/service/ContentService.java b/src/main/java/org/sunbird/common/service/ContentService.java index 64b2a36ac..22d5b6646 100644 --- a/src/main/java/org/sunbird/common/service/ContentService.java +++ b/src/main/java/org/sunbird/common/service/ContentService.java @@ -1,5 +1,4 @@ - package org.sunbird.common.service; import java.util.List; @@ -11,7 +10,7 @@ public interface ContentService { public SunbirdApiResp getHeirarchyResponse(String contentId); - + public SunbirdApiResp getAssessmentHierachyResponse(String assessmentId); public List getParticipantsList(String xAuthUser, List batchIdList); @@ -19,15 +18,19 @@ public interface ContentService { public List getParticipantsForBatch(String xAuthUser, String batchId); public SunbirdApiUserCourseListResp getUserCourseListResponse(String authToken, String userId); - + public SunbirdApiResp getQuestionListDetails(List questionIdList); - + + Map searchLiveContentByContentIds(List contentIds); + public Map searchLiveContent(String contentId); - + public Map getHierarchyResponseMap(String contentId); - + public String getParentIdentifier(String resourceId); - + public String getContentType(String resourceId); -} + public void getLiveContentDetails(List contentIdList, List fields, + Map> contentInfoMap); +} diff --git a/src/main/java/org/sunbird/common/service/ContentServiceImpl.java b/src/main/java/org/sunbird/common/service/ContentServiceImpl.java index b8a6a8b03..0781ba9a0 100644 --- a/src/main/java/org/sunbird/common/service/ContentServiceImpl.java +++ b/src/main/java/org/sunbird/common/service/ContentServiceImpl.java @@ -184,9 +184,8 @@ public Map searchLiveContent(String contentId) { filters.put(Constants.IDENTIFIER, contentId); Map contentRequestValue = new HashMap<>(); contentRequestValue.put(Constants.FILTERS, filters); - contentRequestValue.put(Constants.FIELDS, - Arrays.asList(Constants.IDENTIFIER, Constants.NAME, Constants.PRIMARY_CATEGORY, Constants.BATCHES, - Constants.LEAF_NODES_COUNT, Constants.CONTENT_TYPE_KEY)); + contentRequestValue.put(Constants.FIELDS, Arrays.asList(Constants.IDENTIFIER, Constants.NAME, + Constants.PRIMARY_CATEGORY, Constants.BATCHES, Constants.LEAF_NODES_COUNT, Constants.CONTENT_TYPE_KEY)); Map contentRequest = new HashMap<>(); contentRequest.put(Constants.REQUEST, contentRequestValue); response = outboundRequestHandlerService.fetchResultUsingPost( @@ -238,4 +237,88 @@ public String getContentType(String resourceId) { } return parentContentType; } + + @Override + public Map searchLiveContentByContentIds(List contentIds) { + Map response = null; + HashMap headerValues = new HashMap<>(); + headerValues.put(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON); + Map filters = new HashMap<>(); + filters.put(Constants.PRIMARY_CATEGORY, Arrays.asList(Constants.COURSE)); + filters.put(Constants.STATUS, Arrays.asList(Constants.LIVE)); + filters.put(Constants.IDENTIFIER, contentIds); + Map contentRequestValue = new HashMap<>(); + contentRequestValue.put(Constants.FILTERS, filters); + contentRequestValue.put(Constants.FIELDS, Arrays.asList(Constants.IDENTIFIER, Constants.NAME, + Constants.PRIMARY_CATEGORY, Constants.BATCHES, Constants.LEAF_NODES_COUNT, Constants.CONTENT_TYPE_KEY)); + Map contentRequest = new HashMap<>(); + contentRequest.put(Constants.REQUEST, contentRequestValue); + response = outboundRequestHandlerService.fetchResultUsingPost( + serverConfig.getKmBaseHost() + serverConfig.getKmBaseContentSearch(), contentRequest, headerValues); + if (null != response && Constants.OK.equalsIgnoreCase((String) response.get(Constants.RESPONSE_CODE))) { + return response; + } + return null; + } + + public void getLiveContentDetails(List contentIdList, List fields, + Map> contentInfoMap) { + HashMap headerValues = new HashMap<>(); + headerValues.put(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON); + + Map filters = new HashMap<>(); + filters.put(Constants.PRIMARY_CATEGORY, Arrays.asList(Constants.COURSE)); + filters.put(Constants.STATUS, Arrays.asList(Constants.LIVE)); + Map contentRequest = new HashMap<>(); + contentRequest.put(Constants.FILTERS, filters); + contentRequest.put(Constants.FIELDS, fields); + Map contentReqBody = new HashMap<>(); + contentReqBody.put(Constants.REQUEST, contentRequest); + + try { + for (int i = 0; i < contentIdList.size(); i += 100) { + List courseIdList = contentIdList.subList(i, Math.min(contentIdList.size(), i + 100)); + filters.put(Constants.IDENTIFIER, courseIdList); + + Map apiResponse = outboundRequestHandlerService.fetchResultUsingPost( + serverConfig.getKmBaseHost() + serverConfig.getKmBaseContentSearch(), contentReqBody, + headerValues); + if (null != apiResponse + && Constants.OK.equalsIgnoreCase((String) apiResponse.get(Constants.RESPONSE_CODE))) { + Map result = (Map) apiResponse.get(Constants.RESULT); + int count = (int) result.get(Constants.COUNT); + if (count > 0) { + List> contentList = (List>) result + .get(Constants.CONTENT); + + for (Map content : contentList) { + String id = (String) content.get(Constants.IDENTIFIER); + if (!contentInfoMap.containsKey(id)) { + Map contentInfo = new HashMap(); + contentInfo.put(Constants.COURSE_ID, id); + for (String field : fields) { + if (content.containsKey(field)) { + if (Constants.CREATED_FOR.equalsIgnoreCase(field)) { + List createdFor = (List) content.get(Constants.CREATED_FOR); + contentInfo.put(Constants.COURSE_ORG_ID, createdFor.get(0)); + } else { + if (content.get(field) instanceof Integer) { + int value = (Integer) content.get(field); + contentInfo.put(field, Integer.toString(value)); + } else { + contentInfo.put(field, (String) content.get(field)); + } + } + } + } + contentInfoMap.put(id, contentInfo); + } + } + } + } + } + } catch (Exception e) { + logger.error("Failed to get Content details. Exception: ", e); + } + } } diff --git a/src/main/java/org/sunbird/common/util/Constants.java b/src/main/java/org/sunbird/common/util/Constants.java index 47df4c80e..7b9325cbf 100644 --- a/src/main/java/org/sunbird/common/util/Constants.java +++ b/src/main/java/org/sunbird/common/util/Constants.java @@ -230,6 +230,7 @@ public class Constants { public static final String TABLE_ORGANIZATION = "organisation"; public static final String TABLE_USER_ENROLMENT = "user_enrolments"; public static final String TABLE_USER = "user"; + public static final String TABLE_USER_ROLES = "user_roles"; public static final String TABLE_COURSE_BATCH = "course_batch"; public static final String TABLE_RATINGS = "ratings"; public static final String TABLE_RATINGS_LOOKUP = "ratings_lookup"; @@ -510,6 +511,9 @@ public class Constants { public static final String API_USER_SIGNUP = "api.user.signup"; public static final String API_USER_BULK_UPLOAD = "api.user.bulk.upload"; public static final String API_USER_BULK_UPLOAD_STATUS = "api.user.bulk.upload.status"; + + public static final String API_USER_ENROLMENT_REPORT = "api.user.enrolment.report"; + public static final String API_USER_REPORT = "api.user.report"; public static final String TABLE_USER_BULK_UPLOAD = "user_bulk_upload"; public static final String FILE_NAME = "fileName"; public static final String FILE_PATH = "filePath"; @@ -534,6 +538,29 @@ public class Constants { public static final String LEAF_NODES_COUNT = "leafNodesCount"; public static final String CLIENT_ERROR = "CLIENT_ERROR"; public static final String PARENT = "parent"; + public static final String ORGANISATIONS = "organisations"; + + public static final String CIPHER_ALGORITHM = "AES"; + public static final byte[] CIPHER_KEY = new byte[] { 'T', 'h', 'i', 's', 'A', 's', 'I', 'S', 'e', 'r', 'c', 'e', + 'K', 't', 'e', 'y' }; + public static final List DECRYPTED_FIELDS = Arrays.asList("phone", "email"); + public static final String CREATED_FOR = "createdFor"; + public static final String COURSE_ORG_ID = "courseOrgId"; + public static final String COURSE_ORG_NAME = "courseOrgName"; + public static final String STATUS_ENROLLED = "Enrolled"; + public static final String STAUTS_IN_PROGRESS = "In-Progress"; + public static final String STATUS_COMPLETED = "Completed"; + public static final String CONTENT_STATUS = "contentStatus"; + public static final String ROLE = "role"; + public static final String SCOPE = "scope"; + + public static final List USER_ENROLMENT_REPORT_FIELDS = Arrays.asList(USER_ID, FIRSTNAME, LASTNAME, EMAIL, + PHONE, ROOT_ORG_ID, CHANNEL); + + public static final List COURSE_ENROLMENT_REPORT_FIELDS = Arrays.asList(COURSE_ID, NAME, COURSE_ORG_ID, + COURSE_ORG_NAME); + + public static final List USER_ENROLMENT_COMMON_FIELDS = Arrays.asList(STATUS, COMPLETION_PERCENTAGE); private Constants() { throw new IllegalStateException("Utility class"); diff --git a/src/main/java/org/sunbird/common/util/ProjectUtil.java b/src/main/java/org/sunbird/common/util/ProjectUtil.java index 2fc08ea61..2cb85b92f 100644 --- a/src/main/java/org/sunbird/common/util/ProjectUtil.java +++ b/src/main/java/org/sunbird/common/util/ProjectUtil.java @@ -1,7 +1,5 @@ package org.sunbird.common.util; -import com.fasterxml.jackson.databind.ObjectMapper; - import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; @@ -23,54 +21,50 @@ */ public class ProjectUtil { - public static CbExtLogger logger = new CbExtLogger(ProjectUtil.class.getName()); + public static CbExtLogger logger = new CbExtLogger(ProjectUtil.class.getName()); + + public static PropertiesCache propertiesCache; - public static PropertiesCache propertiesCache; - private static final ObjectMapper mapper = new ObjectMapper(); + static { + propertiesCache = PropertiesCache.getInstance(); + } - static { - propertiesCache = PropertiesCache.getInstance(); - } + public static String getConfigValue(String key) { + if (StringUtils.isNotBlank(System.getenv(key))) { + return System.getenv(key); + } + return propertiesCache.readProperty(key); + } - public static String getConfigValue(String key) { - if (StringUtils.isNotBlank(System.getenv(key))) { - return System.getenv(key); - } - return propertiesCache.readProperty(key); - } + /** + * This method will check incoming value is null or empty it will do empty check + * by doing trim method. in case of null or empty it will return true else + * false. + * + * @param value + * @return + */ + public static boolean isStringNullOREmpty(String value) { + return (value == null || "".equals(value.trim())); + } - /** - * This method will check incoming value is null or empty it will do empty check by doing trim - * method. in case of null or empty it will return true else false. - * - * @param value - * @return - */ - public static boolean isStringNullOREmpty(String value) { - return (value == null || "".equals(value.trim())); - } + /** + * This method will create and return server exception to caller. + * + * @param responseCode ResponseCode + * @return ProjectCommonException + */ + public static ProjectCommonException createServerError(ResponseCode responseCode) { + return new ProjectCommonException(responseCode.getErrorCode(), responseCode.getErrorMessage(), + ResponseCode.SERVER_ERROR.getResponseCode()); + } - /** - * This method will create and return server exception to caller. - * - * @param responseCode ResponseCode - * @return ProjectCommonException - */ - public static ProjectCommonException createServerError(ResponseCode responseCode) { - return new ProjectCommonException( - responseCode.getErrorCode(), - responseCode.getErrorMessage(), - ResponseCode.SERVER_ERROR.getResponseCode()); - } + public static ProjectCommonException createClientException(ResponseCode responseCode) { + return new ProjectCommonException(responseCode.getErrorCode(), responseCode.getErrorMessage(), + ResponseCode.CLIENT_ERROR.getResponseCode()); + } - public static ProjectCommonException createClientException(ResponseCode responseCode) { - return new ProjectCommonException( - responseCode.getErrorCode(), - responseCode.getErrorMessage(), - ResponseCode.CLIENT_ERROR.getResponseCode()); - } - - public static SBApiResponse createDefaultResponse(String api) { + public static SBApiResponse createDefaultResponse(String api) { SBApiResponse response = new SBApiResponse(); response.setId(api); response.setVer(Constants.API_VERSION_1); @@ -81,42 +75,36 @@ public static SBApiResponse createDefaultResponse(String api) { return response; } - public static Map getDefaultHeaders() { + public static Map getDefaultHeaders() { Map headers = new HashMap(); headers.put(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON); return headers; } - - public enum Method { - GET, - POST, - PUT, - DELETE, - PATCH - } - public static String convertSecondsToHrsAndMinutes(int seconds) { - String time = ""; - if (seconds > 60) { - int min = (seconds / 60) % 60; - int hours = (seconds / 60) / 60; - String minutes = (min < 10) ? "0" + min : Integer.toString(min); - String strHours = (hours < 10) ? "0" + hours : Integer.toString(hours); - if (min > 0 && hours > 0) - time = strHours + "h " + minutes + "m"; - else if (min == 0 && hours > 0) - time = strHours + "h"; - else if (min > 0) { - time = minutes + "m"; - } - } - return time; - } - - public static String firstLetterCapitalWithSingleSpace(final String words) { - return Stream.of(words.trim().split("\\s")) - .filter(word -> word.length() > 0) - .map(word -> word.substring(0, 1).toUpperCase() + word.substring(1)) - .collect(Collectors.joining(" ")); - } + public enum Method { + GET, POST, PUT, DELETE, PATCH + } + + public static String convertSecondsToHrsAndMinutes(int seconds) { + String time = ""; + if (seconds > 60) { + int min = (seconds / 60) % 60; + int hours = (seconds / 60) / 60; + String minutes = (min < 10) ? "0" + min : Integer.toString(min); + String strHours = (hours < 10) ? "0" + hours : Integer.toString(hours); + if (min > 0 && hours > 0) + time = strHours + "h " + minutes + "m"; + else if (min == 0 && hours > 0) + time = strHours + "h"; + else if (min > 0) { + time = minutes + "m"; + } + } + return time; + } + + public static String firstLetterCapitalWithSingleSpace(final String words) { + return Stream.of(words.trim().split("\\s")).filter(word -> word.length() > 0) + .map(word -> word.substring(0, 1).toUpperCase() + word.substring(1)).collect(Collectors.joining(" ")); + } } \ No newline at end of file diff --git a/src/main/java/org/sunbird/core/cipher/BASE64Decoder.java b/src/main/java/org/sunbird/core/cipher/BASE64Decoder.java new file mode 100644 index 000000000..e6012588a --- /dev/null +++ b/src/main/java/org/sunbird/core/cipher/BASE64Decoder.java @@ -0,0 +1,104 @@ +package org.sunbird.core.cipher; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PushbackInputStream; + +public class BASE64Decoder extends CharacterDecoder { + /** This class has 4 bytes per atom */ + protected int bytesPerAtom() { + return (4); + } + + /** Any multiple of 4 will do, 72 might be common */ + protected int bytesPerLine() { + return (72); + } + + /** + * This character array provides the character to value map based on RFC1521. + */ + private static final char pem_array[] = { + // 0 1 2 3 4 5 6 7 + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 1 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 2 + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 3 + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 4 + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 5 + 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 6 + '4', '5', '6', '7', '8', '9', '+', '/' // 7 + }; + + private static final byte pem_convert_array[] = new byte[256]; + + static { + for (int i = 0; i < 255; i++) { + pem_convert_array[i] = -1; + } + for (int i = 0; i < pem_array.length; i++) { + pem_convert_array[pem_array[i]] = (byte) i; + } + } + + byte decode_buffer[] = new byte[4]; + + /** Decode one BASE64 atom into 1, 2, or 3 bytes of data. */ + @SuppressWarnings("fallthrough") + protected void decodeAtom(PushbackInputStream inStream, OutputStream outStream, int rem) + throws java.io.IOException { + int i; + byte a = -1, b = -1, c = -1, d = -1; + + if (rem < 2) { + throw new IOException("BASE64Decoder: Not enough bytes for an atom."); + } + do { + i = inStream.read(); + if (i == -1) { + throw new IOException(); + } + } while (i == '\n' || i == '\r'); + decode_buffer[0] = (byte) i; + + i = readFully(inStream, decode_buffer, 1, rem - 1); + if (i == -1) { + throw new IOException(); + } + + if (rem > 3 && decode_buffer[3] == '=') { + rem = 3; + } + if (rem > 2 && decode_buffer[2] == '=') { + rem = 2; + } + switch (rem) { + case 4: + d = pem_convert_array[decode_buffer[3] & 0xff]; + // NOBREAK + case 3: + c = pem_convert_array[decode_buffer[2] & 0xff]; + // NOBREAK + case 2: + b = pem_convert_array[decode_buffer[1] & 0xff]; + a = pem_convert_array[decode_buffer[0] & 0xff]; + break; + } + + switch (rem) { + case 2: + outStream.write((byte) (((a << 2) & 0xfc) | ((b >>> 4) & 3))); + break; + case 3: + outStream.write((byte) (((a << 2) & 0xfc) | ((b >>> 4) & 3))); + outStream.write((byte) (((b << 4) & 0xf0) | ((c >>> 2) & 0xf))); + break; + case 4: + outStream.write((byte) (((a << 2) & 0xfc) | ((b >>> 4) & 3))); + outStream.write((byte) (((b << 4) & 0xf0) | ((c >>> 2) & 0xf))); + outStream.write((byte) (((c << 6) & 0xc0) | (d & 0x3f))); + break; + } + return; + } +} diff --git a/src/main/java/org/sunbird/core/cipher/CharacterDecoder.java b/src/main/java/org/sunbird/core/cipher/CharacterDecoder.java new file mode 100644 index 000000000..b732e722f --- /dev/null +++ b/src/main/java/org/sunbird/core/cipher/CharacterDecoder.java @@ -0,0 +1,140 @@ +package org.sunbird.core.cipher; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PushbackInputStream; +import java.nio.ByteBuffer; + +public abstract class CharacterDecoder { + public CharacterDecoder() { + } + + /** Return the number of bytes per atom of decoding */ + protected abstract int bytesPerAtom(); + + /** Return the maximum number of bytes that can be encoded per line */ + protected abstract int bytesPerLine(); + + /** decode the beginning of the buffer, by default this is a NOP. */ + protected void decodeBufferPrefix(PushbackInputStream aStream, OutputStream bStream) throws IOException { + } + + /** decode the buffer suffix, again by default it is a NOP. */ + protected void decodeBufferSuffix(PushbackInputStream aStream, OutputStream bStream) throws IOException { + } + + /** + * This method should return, if it knows, the number of bytes that will be + * decoded. Many formats such as uuencoding provide this information. By default + * we return the maximum bytes that could have been encoded on the line. + */ + protected int decodeLinePrefix(PushbackInputStream aStream, OutputStream bStream) throws IOException { + return (bytesPerLine()); + } + + /** + * This method post processes the line, if there are error detection or + * correction codes in a line, they are generally processed by this method. The + * simplest version of this method looks for the (newline) character. + */ + protected void decodeLineSuffix(PushbackInputStream aStream, OutputStream bStream) throws IOException { + } + + /** + * This method does an actual decode. It takes the decoded bytes and writes them + * to the OutputStream. The integer l tells the method how many bytes are + * required. This is always <= bytesPerAtom(). + */ + protected void decodeAtom(PushbackInputStream aStream, OutputStream bStream, int l) throws IOException { + throw new IOException(); + } + + /** + * This method works around the bizarre semantics of BufferedInputStream's read + * method. + */ + protected int readFully(InputStream in, byte buffer[], int offset, int len) throws java.io.IOException { + for (int i = 0; i < len; i++) { + int q = in.read(); + if (q == -1) + return ((i == 0) ? -1 : i); + buffer[i + offset] = (byte) q; + } + return len; + } + + /** + * Decode the text from the InputStream and write the decoded octets to the + * OutputStream. This method runs until the stream is exhausted. + * + * @exception IOException An error has occurred while decoding + * @exception IOException The input stream is unexpectedly out of data + */ + public void decodeBuffer(InputStream aStream, OutputStream bStream) throws IOException { + int i; + int totalBytes = 0; + + PushbackInputStream ps = new PushbackInputStream(aStream); + decodeBufferPrefix(ps, bStream); + while (true) { + int length; + + try { + length = decodeLinePrefix(ps, bStream); + for (i = 0; (i + bytesPerAtom()) < length; i += bytesPerAtom()) { + decodeAtom(ps, bStream, bytesPerAtom()); + totalBytes += bytesPerAtom(); + } + if ((i + bytesPerAtom()) == length) { + decodeAtom(ps, bStream, bytesPerAtom()); + totalBytes += bytesPerAtom(); + } else { + decodeAtom(ps, bStream, length - i); + totalBytes += (length - i); + } + decodeLineSuffix(ps, bStream); + } catch (IOException e) { + break; + } + } + decodeBufferSuffix(ps, bStream); + } + + /** + * Alternate decode interface that takes a String containing the encoded buffer + * and returns a byte array containing the data. + * + * @exception IOException An error has occurred while decoding + */ + public byte decodeBuffer(String inputString)[] throws IOException { + byte inputBuffer[] = new byte[inputString.length()]; + ByteArrayInputStream inStream; + ByteArrayOutputStream outStream; + + inputString.getBytes(0, inputString.length(), inputBuffer, 0); + inStream = new ByteArrayInputStream(inputBuffer); + outStream = new ByteArrayOutputStream(); + decodeBuffer(inStream, outStream); + return (outStream.toByteArray()); + } + + /** Decode the contents of the inputstream into a buffer. */ + public byte decodeBuffer(InputStream in)[] throws IOException { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + decodeBuffer(in, outStream); + return (outStream.toByteArray()); + } + + /** Decode the contents of the String into a ByteBuffer. */ + public ByteBuffer decodeBufferToByteBuffer(String inputString) throws IOException { + return ByteBuffer.wrap(decodeBuffer(inputString)); + } + + /** Decode the contents of the inputStream into a ByteBuffer. */ + public ByteBuffer decodeBufferToByteBuffer(InputStream in) throws IOException { + return ByteBuffer.wrap(decodeBuffer(in)); + } +} diff --git a/src/main/java/org/sunbird/core/cipher/DecryptServiceImpl.java b/src/main/java/org/sunbird/core/cipher/DecryptServiceImpl.java new file mode 100644 index 000000000..f1b56bb38 --- /dev/null +++ b/src/main/java/org/sunbird/core/cipher/DecryptServiceImpl.java @@ -0,0 +1,62 @@ +package org.sunbird.core.cipher; + +import java.nio.charset.StandardCharsets; + +import javax.annotation.PostConstruct; +import javax.crypto.Cipher; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.sunbird.common.util.Constants; + +@Component +public class DecryptServiceImpl { + + private int ITERATIONS = 3; + + @Value("${sb.env.chiper.password}") + private String sbChiperPassword; + + private Cipher decryptCipher; + private Cipher encryptCipher; + + @Autowired + private SecretKeySpec secretKeySpec; + + @Value("${user.report.store.path}") + private String userStorePath; + + private Logger log = LoggerFactory.getLogger(getClass().getName()); + + @PostConstruct + private void postConstruct() { + try { + decryptCipher = Cipher.getInstance(Constants.CIPHER_ALGORITHM); + decryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + encryptCipher = Cipher.getInstance(Constants.CIPHER_ALGORITHM); + encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + } catch (Exception e) { + log.error("Failed to construct DecryptServiceImpl object."); + } + } + + public String decryptString(String encStr) { + try { + String dValue = null; + String valueToDecrypt = encStr.trim(); + for (int i = 0; i < ITERATIONS; i++) { + byte[] decodedValue = new BASE64Decoder().decodeBuffer(valueToDecrypt); + byte[] decValue = decryptCipher.doFinal(decodedValue); + dValue = new String(decValue, StandardCharsets.UTF_8).substring(sbChiperPassword.length()); + valueToDecrypt = dValue; + } + return dValue; + } catch (Exception ex) { + log.error("Failed to decrypt value. Exception: ", ex); + } + return null; + } +} diff --git a/src/main/java/org/sunbird/core/cipher/SecretKeySpec.java b/src/main/java/org/sunbird/core/cipher/SecretKeySpec.java new file mode 100644 index 000000000..2268de58a --- /dev/null +++ b/src/main/java/org/sunbird/core/cipher/SecretKeySpec.java @@ -0,0 +1,65 @@ +package org.sunbird.core.cipher; + +import java.security.MessageDigest; +import java.security.spec.KeySpec; +import java.util.Locale; + +import javax.annotation.PostConstruct; +import javax.crypto.SecretKey; + +import org.springframework.stereotype.Component; +import org.sunbird.common.util.Constants; + +@Component +public class SecretKeySpec implements KeySpec, SecretKey { + private static final long serialVersionUID = 6577238317307289933L; + private byte[] key; + private String algorithm; + + @PostConstruct + public void postConstruct() { + this.key = (byte[]) Constants.CIPHER_KEY.clone(); + this.algorithm = Constants.CIPHER_ALGORITHM; + } + + public String getAlgorithm() { + return Constants.CIPHER_ALGORITHM; + } + + public String getFormat() { + return "RAW"; + } + + public byte[] getEncoded() { + return (byte[]) this.key.clone(); + } + + public int hashCode() { + int retval = 0; + + for (int i = 1; i < this.key.length; ++i) { + retval += this.key[i] * i; + } + + return this.algorithm.equalsIgnoreCase("TripleDES") ? retval ^ "desede".hashCode() + : retval ^ this.algorithm.toLowerCase(Locale.ENGLISH).hashCode(); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof SecretKey)) { + return false; + } else { + String thatAlg = ((SecretKey) obj).getAlgorithm(); + if (thatAlg.equalsIgnoreCase(this.algorithm) + || thatAlg.equalsIgnoreCase("DESede") && this.algorithm.equalsIgnoreCase("TripleDES") + || thatAlg.equalsIgnoreCase("TripleDES") && this.algorithm.equalsIgnoreCase("DESede")) { + byte[] thatKey = ((SecretKey) obj).getEncoded(); + return MessageDigest.isEqual(this.key, thatKey); + } else { + return false; + } + } + } +} diff --git a/src/main/java/org/sunbird/org/service/ExtendedOrgService.java b/src/main/java/org/sunbird/org/service/ExtendedOrgService.java index 6c2b0c43d..52ce7d4d2 100644 --- a/src/main/java/org/sunbird/org/service/ExtendedOrgService.java +++ b/src/main/java/org/sunbird/org/service/ExtendedOrgService.java @@ -1,5 +1,6 @@ package org.sunbird.org.service; +import java.util.List; import java.util.Map; import org.sunbird.common.model.SBApiResponse; @@ -10,4 +11,6 @@ public interface ExtendedOrgService { public SBApiResponse createOrg(Map requestData, String userToken); public SBApiResponse orgExtSearch(Map request) throws Exception; + + public void getOrgDetailsFromDB(List orgIds, Map orgInfoMap); } diff --git a/src/main/java/org/sunbird/org/service/ExtendedOrgServiceImpl.java b/src/main/java/org/sunbird/org/service/ExtendedOrgServiceImpl.java index 57715f6a1..e322f132c 100644 --- a/src/main/java/org/sunbird/org/service/ExtendedOrgServiceImpl.java +++ b/src/main/java/org/sunbird/org/service/ExtendedOrgServiceImpl.java @@ -9,6 +9,7 @@ import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -346,4 +347,61 @@ private String findRootOrgId(String orgName, String mapId) { return StringUtils.EMPTY; } + + public Map getOrgDetails(List orgIds, List fields) { + Map filters = new HashMap<>(); + filters.put(Constants.IDENTIFIER, orgIds); + Map requestBody = new HashMap<>(); + requestBody.put(Constants.FILTERS, filters); + requestBody.put(Constants.FIELDS, fields); + Map request = new HashMap<>(); + request.put(Constants.REQUEST, requestBody); + Map headers = new HashMap<>(); + headers.put(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON); + Map apiResponse = (Map) outboundService.fetchResultUsingPost( + configProperties.getSbUrl() + configProperties.getSbOrgSearchPath(), request, headers); + Map orgMap = new HashMap<>(); + if (Constants.OK.equalsIgnoreCase((String) apiResponse.get(Constants.RESPONSE_CODE))) { + Map result = (Map) apiResponse.get(Constants.RESULT); + if (MapUtils.isNotEmpty(result)) { + Map response = (Map) result.get(Constants.RESPONSE); + if (MapUtils.isNotEmpty(response)) { + for (int i = 0; i < orgIds.size(); i++) { + orgMap.put((String) response.get(orgIds.get(i)), response.get(Constants.CONTENT)); + } + } + } + } + return orgMap; + } + + public void getOrgDetailsFromDB(List orgIds, Map orgInfoMap) { + Map propertyMap = new HashMap<>(); + propertyMap.put(Constants.STATUS, 1); + + try { + for (int i = 0; i < orgIds.size(); i += 10) { + List orgList = orgIds.subList(i, Math.min(orgIds.size(), i + 10)); + propertyMap.put(Constants.ID, orgList); + + List> orgInfoList = cassandraOperation.getRecordsByProperties( + Constants.KEYSPACE_SUNBIRD, Constants.TABLE_ORGANIZATION, propertyMap, + Arrays.asList(Constants.CHANNEL)); + for (Map org : orgInfoList) { + String orgId = (String) org.get(Constants.ID); + + if (orgInfoMap.containsKey(orgId)) { + continue; + } + + if (org.containsKey(Constants.CHANNEL)) { + + orgInfoMap.put(orgId, (String) org.get(Constants.CHANNEL)); + } + } + } + } catch (Exception e) { + log.error("Failed to get user details from DB. Exception: ", e); + } + } } diff --git a/src/main/java/org/sunbird/profile/controller/ProfileController.java b/src/main/java/org/sunbird/profile/controller/ProfileController.java index 56611e770..9eed011e4 100644 --- a/src/main/java/org/sunbird/profile/controller/ProfileController.java +++ b/src/main/java/org/sunbird/profile/controller/ProfileController.java @@ -89,4 +89,16 @@ public ResponseEntity getBulkUploadDetails(@PathVariable("orgId") String orgI SBApiResponse response = profileService.getBulkUploadDetails(orgId); return new ResponseEntity<>(response, response.getResponseCode()); } + + @GetMapping("/user/v1/enrollement/report") + public ResponseEntity getUserEnrollmentReport() { + SBApiResponse response = profileService.getUserEnrollmentReport(); + return new ResponseEntity<>(response, response.getResponseCode()); + } + + @GetMapping("/user/v1/report") + public ResponseEntity generateUserReport() { + SBApiResponse response = profileService.getUserReport(); + return new ResponseEntity<>(response, response.getResponseCode()); + } } diff --git a/src/main/java/org/sunbird/profile/service/ProfileService.java b/src/main/java/org/sunbird/profile/service/ProfileService.java index d885c50de..edcfbc652 100644 --- a/src/main/java/org/sunbird/profile/service/ProfileService.java +++ b/src/main/java/org/sunbird/profile/service/ProfileService.java @@ -25,4 +25,8 @@ public interface ProfileService { SBApiResponse bulkUpload(MultipartFile mFile, String orgId, String orgName, String userId); SBApiResponse getBulkUploadDetails(String orgId); + + SBApiResponse getUserEnrollmentReport(); + + SBApiResponse getUserReport(); } diff --git a/src/main/java/org/sunbird/profile/service/ProfileServiceImpl.java b/src/main/java/org/sunbird/profile/service/ProfileServiceImpl.java index b9b48e22a..4cd26592a 100644 --- a/src/main/java/org/sunbird/profile/service/ProfileServiceImpl.java +++ b/src/main/java/org/sunbird/profile/service/ProfileServiceImpl.java @@ -1,15 +1,22 @@ package org.sunbird.profile.service; +import static java.util.stream.Collectors.toList; + import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.ListUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.elasticsearch.action.search.SearchResponse; @@ -30,6 +37,7 @@ import org.sunbird.cassandra.utils.CassandraOperation; import org.sunbird.common.model.SBApiResponse; import org.sunbird.common.model.SunbirdApiRespParam; +import org.sunbird.common.service.ContentService; import org.sunbird.common.service.OutboundRequestHandlerServiceImpl; import org.sunbird.common.util.CbExtServerProperties; import org.sunbird.common.util.Constants; @@ -38,7 +46,8 @@ import org.sunbird.common.util.PropertiesCache; import org.sunbird.org.service.ExtendedOrgService; import org.sunbird.storage.service.StorageServiceImpl; -import org.sunbird.user.service.UserUtilityServiceImpl; +import org.sunbird.user.report.UserReportService; +import org.sunbird.user.service.UserUtilityService; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -57,7 +66,7 @@ public class ProfileServiceImpl implements ProfileService { RedisCacheMgr redisCacheMgr; @Autowired - UserUtilityServiceImpl userUtilityService; + UserUtilityService userUtilityService; @Autowired ObjectMapper mapper; @@ -74,8 +83,16 @@ public class ProfileServiceImpl implements ProfileService { @Autowired StorageServiceImpl storageService; + @Autowired + ContentService contentService; + + @Autowired + UserReportService reportService; + private Logger log = LoggerFactory.getLogger(getClass().getName()); + private ObjectMapper ob = new ObjectMapper(); + @Override public SBApiResponse profileUpdate(Map request, String userToken, String authToken) throws Exception { @@ -577,7 +594,7 @@ public SBApiResponse userSignup(Map request) { requestBody.put(Constants.EMAIL_VERIFIED, true); Map readData = (Map) outboundRequestHandlerService.fetchResultUsingPost( serverConfig.getSbUrl() + serverConfig.getLmsUserSignUpPath(), request, - userUtilityService.getDefaultHeaders()); + ProjectUtil.getDefaultHeaders()); if (Constants.OK.equalsIgnoreCase((String) readData.get(Constants.RESPONSE_CODE))) { Map result = (Map) readData.get(Constants.RESULT); String userId = (String) result.get(Constants.USER_ID); @@ -1138,7 +1155,7 @@ private boolean updateUser(Map requestObject) { updateRequest.put(Constants.REQUEST, updateRequestBody); Map updateReadData = (Map) outboundRequestHandlerService.fetchResultUsingPatch( serverConfig.getSbUrl() + serverConfig.getLmsUserUpdatePath(), updateRequest, - userUtilityService.getDefaultHeaders()); + ProjectUtil.getDefaultHeaders()); if (Constants.OK.equalsIgnoreCase((String) updateReadData.get(Constants.RESPONSE_CODE))) { Map roleMap = new HashMap<>(); roleMap.put(Constants.ORGANIZATION_ID, requestObject.get(Constants.ROOT_ORG_ID)); @@ -1158,7 +1175,7 @@ private boolean assignRole(Map request) { requestObj.put(Constants.REQUEST, requestBody); Map readData = (Map) outboundRequestHandlerService.fetchResultUsingPost( serverConfig.getSbUrl() + serverConfig.getSbAssignRolePath(), requestObj, - userUtilityService.getDefaultHeaders()); + ProjectUtil.getDefaultHeaders()); if (readData.isEmpty() == Boolean.FALSE) { if (Constants.OK.equalsIgnoreCase((String) readData.get(Constants.RESPONSE_CODE))) retValue = Boolean.TRUE; @@ -1215,4 +1232,277 @@ private void sendBulkUploadNotification(String orgId, String orgName, String fil serverConfig.getSbUrl() + serverConfig.getSbSendNotificationEmailPath(), request, ProjectUtil.getDefaultHeaders()); } + + @Override + public SBApiResponse getUserEnrollmentReport() { + log.info("Starting user enrolment report..."); + SBApiResponse response = ProjectUtil.createDefaultResponse(Constants.API_USER_ENROLMENT_REPORT); + Map propertyMap = new HashMap<>(); + propertyMap.put(Constants.ACTIVE, Boolean.TRUE); + + // Use the following map to construct the excel report + Map> userInfoMap = new HashMap>(); + Map> courseInfoMap = new HashMap>(); + Map orgInfoMap = new HashMap(); + + try { + List> userEnrolmentList = cassandraOperation.getRecordsByProperties( + Constants.KEYSPACE_SUNBIRD_COURSES, Constants.TABLE_USER_ENROLMENT, propertyMap, + Arrays.asList(Constants.USER_ID_CONSTANT, Constants.COURSE_ID, Constants.BATCH_ID, + Constants.COMPLETION_PERCENTAGE, Constants.PROGRESS, Constants.STATUS, + Constants.CONTENT_STATUS)); + if (CollectionUtils.isEmpty(userEnrolmentList)) { + log.info("No records found in the user enolment table."); + return response; + } + log.info(String.format("Found %s records in user enrolment table.", userEnrolmentList.size())); + + List enrolledUserIdList = userEnrolmentList.stream() + .map(obj -> (String) obj.get(Constants.USER_ID_CONSTANT)).collect(Collectors.toList()); + List userIdsDistinct = enrolledUserIdList.stream().distinct().collect(toList()); + log.info(String.format("Found %s unique users in user enrolment table.", userIdsDistinct.size())); + enrichUserDetails(userIdsDistinct, userInfoMap, orgInfoMap); + log.info(String.format("Enriched %s records in userInfo and %s records in orgInfo", userInfoMap.size(), + orgInfoMap.size())); + + List enrolledCourseIdList = userEnrolmentList.stream() + .map(obj -> (String) obj.get(Constants.COURSE_ID)).collect(Collectors.toList()); + List courseIdsDistinct = enrolledCourseIdList.stream().distinct().collect(toList()); + log.info(String.format("Found %s unique courses in user enrolment table.", courseIdsDistinct.size())); + enrichCourseDetails(courseIdsDistinct, courseInfoMap, orgInfoMap); + log.info(String.format("Enriched %s records in courseInfo and %s records in orgInfo", courseInfoMap.size(), + orgInfoMap.size())); + + Map> userEnrolmentMap = new HashMap>(); + // Construct the userEnrolment Map; + for (Map enrolment : userEnrolmentList) { + Map enrolmentReport = new HashMap(); + // Get user details + String userId = (String) enrolment.get(Constants.USER_ID); + String courseId = (String) enrolment.get(Constants.COURSE_ID); + String batchId = (String) enrolment.get(Constants.BATCH_ID); + String enrolmentId = String.format("%s:%s:%s", userId, courseId, batchId); + + boolean isInfoAvailable = true; + if (userInfoMap.containsKey(userId)) { + copyReportDetails(enrolmentReport, userInfoMap.get(userId), Constants.USER_CONST); + } else { + log.error(String.format( + "Failed to get user details for Id: %s, this user may have deactivated. Skipping user. ", + userId)); + isInfoAvailable = false; + } + + if (courseInfoMap.containsKey(courseId)) { + copyReportDetails(enrolmentReport, courseInfoMap.get(courseId), Constants.COURSE); + } else { + log.error(String.format( + "Failed to get course details for Id: %s, this user may have deactivated. Skipping record. ", + courseId)); + isInfoAvailable = false; + } + + if (!isInfoAvailable) { + log.error(String.format( + "Failed to enrich basic Details. Skipping record for UserId: %s, CourseId: %s, BatchId: %s", + userId, courseId, batchId)); + continue; + } + + userEnrolmentMap.put(enrolmentId, enrolmentReport); + + Integer status = (Integer) enrolment.get(Constants.STATUS); + String strStatus = StringUtils.EMPTY; + switch (status) { + case 0: + strStatus = Constants.STATUS_ENROLLED; + break; + case 1: + strStatus = Constants.STAUTS_IN_PROGRESS; + break; + case 2: + strStatus = Constants.STATUS_COMPLETED; + break; + default: + strStatus = "NA"; + } + enrolmentReport.put(Constants.STATUS, strStatus); + + Map contentStatus = (Map) enrolment.get(Constants.CONTENT_STATUS); + if (ObjectUtils.isEmpty(contentStatus)) { + enrolmentReport.put(Constants.COMPLETION_PERCENTAGE, Integer.toString(0)); + } else { + int leafNodeCount = 1; + if (courseInfoMap.containsKey(courseId)) { + String strLeafNode = courseInfoMap.get(courseId).get(Constants.LEAF_NODES_COUNT); + if (StringUtils.isNotBlank(strLeafNode)) { + try { + leafNodeCount = Integer.parseInt(strLeafNode); + } catch (NumberFormatException nfe) { + } + } + } + + float completionPercentage = ((float) contentStatus.size() / leafNodeCount) * 100f; + enrolmentReport.put(Constants.COMPLETION_PERCENTAGE, Float.toString(completionPercentage)); + } + } + + List reportFields = new ArrayList(); + reportFields.addAll(Constants.USER_ENROLMENT_REPORT_FIELDS); + reportFields.addAll(Constants.COURSE_ENROLMENT_REPORT_FIELDS); + reportFields.addAll(Constants.USER_ENROLMENT_COMMON_FIELDS); + reportService.generateUserEnrolmentReport(userEnrolmentMap, reportFields, response); + } catch (Exception e) { + log.error("Failed to generate report. Exception: ", e); + response.getParams().setStatus(Constants.FAILED); + response.getParams().setErrmsg("Failed to generate report."); + response.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR); + } + return response; + } + + public SBApiResponse getUserReport() { + log.info("Starting user report..."); + long startTime = System.currentTimeMillis(); + SBApiResponse response = ProjectUtil.createDefaultResponse(Constants.API_USER_REPORT); + try { + List fields = new ArrayList(); + fields.addAll(Constants.USER_ENROLMENT_REPORT_FIELDS); + fields.add(Constants.ROLES); + + Map> userInfoMap = new HashMap>(); + + cassandraOperation.getAllRecords(Constants.SUNBIRD_KEY_SPACE_NAME, Constants.TABLE_USER, Constants.USER_ENROLMENT_REPORT_FIELDS, + Constants.USER_ID, userInfoMap); + + userUtilityService.enrichUserInfo(fields, userInfoMap); + enrichUserRoles(userInfoMap); + reportService.generateUserReport(userInfoMap, fields, response); + + } catch (Exception e) { + log.error("Failed to generate user report. Exception: ", e); + response.getParams().setStatus(Constants.FAILED); + response.getParams().setErrmsg("Failed to generate report."); + response.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR); + } + log.info(String.format("Generate User Report Competed Oeration in %s seconds", + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - startTime))); + return response; + } + + private void enrichUserDetails(List userIdList, Map> userInfoMap, + Map orgInfoMap) { + long startTime = System.currentTimeMillis(); + userUtilityService.getUserDetailsFromDB(userIdList, Constants.USER_ENROLMENT_REPORT_FIELDS, userInfoMap); + log.info(String.format("User enrichment took %s seconds", (System.currentTimeMillis() - startTime) / 1000)); + startTime = System.currentTimeMillis(); + Iterator>> it = userInfoMap.entrySet().iterator(); + while (it.hasNext()) { + Entry> item = it.next(); + String orgId = item.getValue().get(Constants.ROOT_ORG_ID); + if (!orgInfoMap.containsKey(orgId)) { + orgInfoMap.put(orgId, item.getValue().get(Constants.CHANNEL)); + } + } + log.info(String.format("Org enrichment took %s seconds", (System.currentTimeMillis() - startTime) / 1000)); + } + + private void enrichCourseDetails(List courseIdList, Map> courseInfoMap, + Map orgInfoMap) { + + contentService.getLiveContentDetails(courseIdList, + Arrays.asList(Constants.IDENTIFIER, Constants.NAME, Constants.CREATED_FOR, Constants.LEAF_NODES_COUNT), + courseInfoMap); + + Iterator>> it = courseInfoMap.entrySet().iterator(); + + List orgIdList = new ArrayList(); + while (it.hasNext()) { + Entry> item = it.next(); + String orgId = item.getValue().get(Constants.COURSE_ORG_ID); + if (orgInfoMap.containsKey(orgId)) { + item.getValue().put(Constants.COURSE_ORG_NAME, orgInfoMap.get(orgId)); + } else if (StringUtils.isNotBlank(orgId)) { + orgIdList.add(orgId); + } + } + + if (orgIdList.size() > 0) { + extOrgService.getOrgDetailsFromDB(orgIdList, orgInfoMap); + it = courseInfoMap.entrySet().iterator(); + while (it.hasNext()) { + Entry> item = it.next(); + String orgId = item.getValue().get(Constants.COURSE_ORG_ID); + if (orgInfoMap.containsKey(orgId)) { + item.getValue().put(Constants.COURSE_ORG_NAME, orgInfoMap.get(orgId)); + } + } + } + } + + private void copyReportDetails(Map enrolmentReport, Map objectInfo, + String objectType) { + List fields = ListUtils.EMPTY_LIST; + switch (objectType) { + case Constants.USER_CONST: { + fields = Constants.USER_ENROLMENT_REPORT_FIELDS; + break; + } + case Constants.COURSE: { + fields = Constants.COURSE_ENROLMENT_REPORT_FIELDS; + break; + } + } + for (String field : fields) { + if (objectInfo.containsKey(field)) { + enrolmentReport.put(field, objectInfo.get(field)); + } else { + enrolmentReport.put(field, StringUtils.EMPTY); + } + } + } + + private void enrichUserRoles(Map> userInfoMap) { + Iterator userIdSet = userInfoMap.keySet().iterator(); + while (userIdSet.hasNext()) { + int i = 0; + List userIds = new ArrayList(); + while (i++ < 10 && userIdSet.hasNext()) { + userIds.add(userIdSet.next()); + } + Map propertyMap = new HashMap<>(); + propertyMap.put(Constants.USER_ID, userIds); + + List> userRoleList = cassandraOperation.getRecordsByProperties( + Constants.KEYSPACE_SUNBIRD, Constants.TABLE_USER_ROLES, propertyMap, + Arrays.asList(Constants.USER_ID, Constants.ROLE, Constants.SCOPE)); + + for (Map userRole : userRoleList) { + String userId = (String) userRole.get(Constants.USER_ID); + try { + String dbScope = (String) userRole.get(Constants.SCOPE); + if (dbScope != null && dbScope.length() > 0) { + List> scopeList = ob.readValue(dbScope, ArrayList.class); + for (Map scopeObj : scopeList) { + String orgId = scopeObj.get("organisationId"); + if (StringUtils.isNotBlank(orgId) + && orgId.equalsIgnoreCase(userInfoMap.get(userId).get(Constants.ROOT_ORG_ID))) { + String existingRoles = userInfoMap.get(userId).get(Constants.ROLES); + if (StringUtils.isNotBlank(existingRoles)) { + existingRoles = existingRoles.concat(", ") + .concat((String) userRole.get(Constants.ROLE)); + } else { + existingRoles = (String) userRole.get(Constants.ROLE); + } + userInfoMap.get(userId).put(Constants.ROLES, existingRoles); + } + } + } + } catch (Exception e) { + log.error("Failed to process role. Exception: ", e); + } + } + } + } } diff --git a/src/main/java/org/sunbird/user/report/UserReportService.java b/src/main/java/org/sunbird/user/report/UserReportService.java new file mode 100644 index 000000000..4efe3269f --- /dev/null +++ b/src/main/java/org/sunbird/user/report/UserReportService.java @@ -0,0 +1,15 @@ +package org.sunbird.user.report; + +import java.util.List; +import java.util.Map; + +import org.sunbird.common.model.SBApiResponse; + +public interface UserReportService { + + public void generateUserEnrolmentReport(Map> userEnrolmentMap, List fields, + SBApiResponse response); + + public void generateUserReport(Map> userInfoMap, List fields, + SBApiResponse response); +} diff --git a/src/main/java/org/sunbird/user/report/UserReportServiceImpl.java b/src/main/java/org/sunbird/user/report/UserReportServiceImpl.java new file mode 100644 index 000000000..acd21987e --- /dev/null +++ b/src/main/java/org/sunbird/user/report/UserReportServiceImpl.java @@ -0,0 +1,132 @@ +package org.sunbird.user.report; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.sunbird.common.model.SBApiResponse; +import org.sunbird.common.util.Constants; + +@Service +public class UserReportServiceImpl implements UserReportService { + + private Logger log = LoggerFactory.getLogger(getClass().getName()); + + @Value("${user.report.store.path}") + private String userStorePath; + + public void generateUserEnrolmentReport(Map> userEnrolmentMap, List fields, + SBApiResponse response) { + log.info("UserReportServiceImpl:: generateUserEnrolmentReport started"); + long startTime = System.currentTimeMillis(); + + Workbook wb = new XSSFWorkbook(); + Sheet sheet = wb.createSheet("KarmayogiBharat User Enrolment Details"); + int rowNum = 0; + Row row = sheet.createRow(rowNum++); + int cellNum = 0; + for (String fieldName : fields) { + Cell cell = row.createCell(cellNum++); + cell.setCellValue(fieldName); + } + + Iterator>> it = userEnrolmentMap.entrySet().iterator(); + while (it.hasNext()) { + Entry> entry = it.next(); + Row rowE = sheet.createRow(rowNum++); + cellNum = 0; + for (String field : fields) { + Cell cell = rowE.createCell(cellNum++); + String cellVal = entry.getValue().get(field); + if (StringUtils.isNotBlank(cellVal)) { + cell.setCellValue(cellVal); + } else { + cell.setCellValue(""); + } + } + } + + String fileName = userStorePath + "/userEnrolmentReport-" + java.time.LocalDate.now() + + System.currentTimeMillis() + ".xlsx"; + log.info("Constructed File Name -> " + fileName); + + try { + File file = new File(fileName); + file.createNewFile(); + OutputStream fileOut = new FileOutputStream(file, false); + wb.write(fileOut); + wb.close(); + response.getResult().put(Constants.FILE_NAME, fileName); + } catch (Exception e) { + log.error("Failed to write the workbook created for UserEnrolment Report. Exception: ", e); + } + + log.info(String.format( + "UserReportServiceImpl:: generateUserEnrolmentReport started and it took %s seconds to complete.", + (System.currentTimeMillis() - startTime) / 1000)); + } + + public void generateUserReport(Map> userInfoMap, List fields, + SBApiResponse response) { + log.info("UserReportServiceImpl:: generateUserReport started"); + long startTime = System.currentTimeMillis(); + + Workbook wb = new XSSFWorkbook(); + Sheet sheet = wb.createSheet("KarmayogiBharat User Details"); + int rowNum = 0; + Row row = sheet.createRow(rowNum++); + int cellNum = 0; + for (String fieldName : fields) { + Cell cell = row.createCell(cellNum++); + cell.setCellValue(fieldName); + } + + Iterator>> it = userInfoMap.entrySet().iterator(); + while (it.hasNext()) { + Entry> entry = it.next(); + Row rowE = sheet.createRow(rowNum++); + cellNum = 0; + for (String field : fields) { + Cell cell = rowE.createCell(cellNum++); + String cellVal = entry.getValue().get(field); + if (StringUtils.isNotBlank(cellVal)) { + cell.setCellValue(cellVal); + } else { + cell.setCellValue(""); + } + } + } + + String fileName = userStorePath + "/userReport-" + java.time.LocalDate.now() + System.currentTimeMillis() + + ".xlsx"; + log.info("Constructed File Name -> " + fileName); + + try { + File file = new File(fileName); + file.createNewFile(); + OutputStream fileOut = new FileOutputStream(file, false); + wb.write(fileOut); + wb.close(); + response.getResult().put(Constants.FILE_NAME, fileName); + } catch (Exception e) { + log.error("Failed to write the workbook created for UserEnrolment Report. Exception: ", e); + } + + log.info(String.format("UserReportServiceImpl:: generateUserReport started and it took %s seconds to complete.", + (System.currentTimeMillis() - startTime) / 1000)); + } +} diff --git a/src/main/java/org/sunbird/user/service/UserUtilityService.java b/src/main/java/org/sunbird/user/service/UserUtilityService.java index 619c190bd..ea4a2305f 100644 --- a/src/main/java/org/sunbird/user/service/UserUtilityService.java +++ b/src/main/java/org/sunbird/user/service/UserUtilityService.java @@ -27,4 +27,13 @@ public interface UserUtilityService { boolean getActivationLink(UserRegistration userRegistration); boolean createNodeBBUser(UserRegistration userRegistration); + + public boolean assignRole(String sbOrgId, String userId, String objectDetails); + + public Map> getUserDetails(List userIds, List fields); + + public void getUserDetailsFromDB(List userIds, List fields, + Map> userInfoMap); + + public void enrichUserInfo(List fields, Map> userInfoMap); } \ No newline at end of file diff --git a/src/main/java/org/sunbird/user/service/UserUtilityServiceImpl.java b/src/main/java/org/sunbird/user/service/UserUtilityServiceImpl.java index 36f693d59..0616d4a8c 100644 --- a/src/main/java/org/sunbird/user/service/UserUtilityServiceImpl.java +++ b/src/main/java/org/sunbird/user/service/UserUtilityServiceImpl.java @@ -5,9 +5,12 @@ import java.util.Arrays; import java.util.Date; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,9 +26,12 @@ import org.sunbird.common.model.SearchUserApiResp; import org.sunbird.common.model.SunbirdApiRequest; import org.sunbird.common.model.SunbirdApiResp; +import org.sunbird.common.model.SunbirdUserProfileDetail; import org.sunbird.common.service.OutboundRequestHandlerServiceImpl; import org.sunbird.common.util.CbExtServerProperties; import org.sunbird.common.util.Constants; +import org.sunbird.common.util.ProjectUtil; +import org.sunbird.core.cipher.DecryptServiceImpl; import org.sunbird.core.exception.ApplicationLogicError; import org.sunbird.telemetry.model.LastLoginInfo; import org.sunbird.user.registration.model.UserRegistration; @@ -58,6 +64,9 @@ public class UserUtilityServiceImpl implements UserUtilityService { @Autowired CbExtServerProperties serverConfig; + @Autowired + DecryptServiceImpl decryptService; + private Logger logger = LoggerFactory.getLogger(UserUtilityServiceImpl.class); public boolean validateUser(String userId) { @@ -127,11 +136,8 @@ public Map getUsersDataFromUserIds(String rootOrg, List @Override public Map getUsersDataFromUserIds(List userIds, List fields, String authToken) { Map result = new HashMap<>(); - // headers HttpHeaders headers = new HttpHeaders(); - headers.add(Constants.USER_TOKEN, authToken); - headers.add(Constants.AUTHORIZATION, props.getSbApiKey()); // request body SunbirdApiRequest requestObj = new SunbirdApiRequest(); Map reqMap = new HashMap<>(); @@ -151,6 +157,9 @@ public Map getUsersDataFromUserIds(List userIds, List 0) { for (SearchUserApiContent searchUserApiContent : searchUserResult.getResult().getResponse() .getContent()) { + if (searchUserApiContent.getProfileDetails() == null) { + searchUserApiContent.setProfileDetails(new SunbirdUserProfileDetail()); + } result.put(searchUserApiContent.getUserId(), searchUserApiContent); } } @@ -217,7 +226,7 @@ public boolean createUser(UserRegistration userRegistration) { request.put(Constants.REQUEST, requestBody); try { Map readData = (Map) outboundRequestHandlerService.fetchResultUsingPost( - props.getSbUrl() + props.getLmsUserCreatePath(), request, getDefaultHeaders()); + props.getSbUrl() + props.getLmsUserCreatePath(), request, ProjectUtil.getDefaultHeaders()); if (Constants.OK.equalsIgnoreCase((String) readData.get(Constants.RESPONSE_CODE))) { Map result = (Map) readData.get(Constants.RESULT); userRegistration.setUserId((String) result.get(Constants.USER_ID)); @@ -264,8 +273,8 @@ public boolean updateUser(UserRegistration userRegistration) { requestBody.put(Constants.PROFILE_DETAILS, profileDetails); request.put(Constants.REQUEST, requestBody); - Map readData = (Map) outboundRequestHandlerService - .fetchResultUsingPatch(props.getSbUrl() + props.getLmsUserUpdatePath(), request, getDefaultHeaders()); + Map readData = (Map) outboundRequestHandlerService.fetchResultUsingPatch( + props.getSbUrl() + props.getLmsUserUpdatePath(), request, ProjectUtil.getDefaultHeaders()); if (Constants.OK.equalsIgnoreCase((String) readData.get(Constants.RESPONSE_CODE))) { retValue = assignRole(userRegistration.getSbOrgId(), userRegistration.getUserId(), userRegistration.toMininumString()); @@ -285,8 +294,8 @@ public boolean assignRole(String sbOrgId, String userId, String objectDetails) { requestBody.put(Constants.USER_ID, userId); requestBody.put(Constants.ROLES, Arrays.asList(Constants.PUBLIC)); request.put(Constants.REQUEST, requestBody); - Map readData = (Map) outboundRequestHandlerService - .fetchResultUsingPost(props.getSbUrl() + props.getSbAssignRolePath(), request, getDefaultHeaders()); + Map readData = (Map) outboundRequestHandlerService.fetchResultUsingPost( + props.getSbUrl() + props.getSbAssignRolePath(), request, ProjectUtil.getDefaultHeaders()); if (Constants.OK.equalsIgnoreCase((String) readData.get(Constants.RESPONSE_CODE))) { retValue = true; } @@ -306,7 +315,8 @@ public boolean createNodeBBUser(UserRegistration userRegistration) { request.put(Constants.REQUEST, requestBody); Map readData = (Map) outboundRequestHandlerService.fetchResultUsingPost( - props.getDiscussionHubHost() + props.getDiscussionHubCreateUserPath(), request, getDefaultHeaders()); + props.getDiscussionHubHost() + props.getDiscussionHubCreateUserPath(), request, + ProjectUtil.getDefaultHeaders()); if (Constants.OK.equalsIgnoreCase((String) readData.get(Constants.RESPONSE_CODE))) { retValue = getActivationLink(userRegistration); } @@ -323,8 +333,8 @@ public boolean getActivationLink(UserRegistration userRegistration) { requestBody.put(Constants.KEY, Constants.EMAIL); requestBody.put(Constants.TYPE, Constants.EMAIL); request.put(Constants.REQUEST, requestBody); - Map readData = (Map) outboundRequestHandlerService - .fetchResultUsingPost(props.getSbUrl() + props.getSbResetPasswordPath(), request, getDefaultHeaders()); + Map readData = (Map) outboundRequestHandlerService.fetchResultUsingPost( + props.getSbUrl() + props.getSbResetPasswordPath(), request, ProjectUtil.getDefaultHeaders()); if (Constants.OK.equalsIgnoreCase((String) readData.get(Constants.RESPONSE_CODE))) { Map result = (Map) readData.get(Constants.RESULT); if (!CollectionUtils.isEmpty(result)) { @@ -354,7 +364,7 @@ public boolean sendWelcomeEmail(String activationLink, UserRegistration userRegi request.put(Constants.REQUEST, requestBody); Map readData = (Map) outboundRequestHandlerService.fetchResultUsingPost( - props.getSbUrl() + props.getSbSendNotificationEmailPath(), request, getDefaultHeaders()); + props.getSbUrl() + props.getSbSendNotificationEmailPath(), request, ProjectUtil.getDefaultHeaders()); if (Constants.OK.equalsIgnoreCase((String) readData.get(Constants.RESPONSE_CODE))) { retValue = true; } @@ -375,9 +385,121 @@ private void printMethodExecutionResult(String methodAction, String objectDetail logger.info(strBuilder.toString()); } - public Map getDefaultHeaders() { - Map headers = new HashMap(); - headers.put(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON); - return headers; + @Override + public Map> getUserDetails(List userIds, List fields) { + // request body + SunbirdApiRequest requestObj = new SunbirdApiRequest(); + Map reqMap = new HashMap<>(); + reqMap.put(Constants.FILTERS, new HashMap() { + { + put(Constants.USER_ID, userIds); + } + }); + reqMap.put(Constants.FIELDS_CONSTANT, fields); + requestObj.setRequest(reqMap); + + try { + String url = props.getSbUrl() + props.getUserSearchEndPoint(); + Map apiResponse = outboundRequestHandlerService.fetchResultUsingPost(url, requestObj, null); + + if (Constants.OK.equalsIgnoreCase((String) apiResponse.get(Constants.RESPONSE_CODE))) { + Map result = (Map) apiResponse.get(Constants.RESULT); + Map searchResponse = (Map) result.get(Constants.RESPONSE); + int count = (int) searchResponse.get(Constants.COUNT); + if (count > 0) { + Map> userDetailsMap = new HashMap>(); + List> userProfiles = (List>) searchResponse + .get(Constants.CONTENT); + if (!CollectionUtils.isEmpty(userProfiles)) { + for (Map userProfile : userProfiles) { + if (userProfile.get("profileDetails") != null) { + HashMap profileDetails = (HashMap) userProfile + .get("profileDetails"); + HashMap personalDetails = (HashMap) profileDetails + .get("personalDetails"); + Map record = new HashMap<>(); + record.put(Constants.USER_ID, (String) userProfile.get(Constants.IDENTIFIER)); + record.put(Constants.FIRSTNAME, + (String) personalDetails.get(Constants.FIRST_NAME_LOWER_CASE)); + record.put(Constants.LASTNAME, (String) personalDetails.get(Constants.SURNAME)); + record.put(Constants.EMAIL, (String) personalDetails.get(Constants.PRIMARY_EMAIL)); + record.put(Constants.ROOT_ORG_ID, (String) userProfile.get(Constants.ROOT_ORG_ID)); + record.put(Constants.CHANNEL, (String) userProfile.get(Constants.CHANNEL)); + + userDetailsMap.put(record.get(Constants.USER_ID), record); + } + } + } + return userDetailsMap; + } + } + } catch (Exception e) { + logger.error("Failed to get user details. Exception: ", e); + } + + return MapUtils.EMPTY_MAP; + } + + @Override + public void getUserDetailsFromDB(List userIds, List fields, + Map> userInfoMap) { + Map propertyMap = new HashMap<>(); + propertyMap.put(Constants.STATUS, 1); + + try { + for (int i = 0; i < userIds.size(); i += 10) { + List userList = userIds.subList(i, Math.min(userIds.size(), i + 10)); + propertyMap.put(Constants.ID, userList); + + List> userInfoList = cassandraOperation + .getRecordsByProperties(Constants.KEYSPACE_SUNBIRD, Constants.TABLE_USER, propertyMap, fields); + for (Map user : userInfoList) { + Map userMap = new HashMap(); + String userId = (String) user.get(Constants.USER_ID); + + if (userInfoMap.containsKey(userId)) { + continue; + } + + for (String field : fields) { + if (user.containsKey(field)) { + if (Constants.DECRYPTED_FIELDS.contains(field)) { + if (StringUtils.isNotBlank((String) user.get(field))) { + String value = decryptService.decryptString((String) user.get(field)); + if (StringUtils.isBlank(value)) { + logger.error( + String.format("Invalid valid for field %s for user %s", field, userId)); + } + userMap.put(field, value); + } + } else { + userMap.put(field, (String) user.get(field)); + } + } + } + userInfoMap.put(userId, userMap); + } + } + } catch (Exception e) { + logger.error("Failed to get user details from DB. Exception: ", e); + } + } + + public void enrichUserInfo(List fields, Map> userInfoMap) { + Iterator>> it = userInfoMap.entrySet().iterator(); + while (it.hasNext()) { + Entry> item = it.next(); + + for (String field : fields) { + if (item.getValue().containsKey(field)) { + if (Constants.DECRYPTED_FIELDS.contains(field)) { + if (StringUtils.isNotBlank((String) item.getValue().get(field))) { + String value = decryptService.decryptString((String) item.getValue().get(field)); + item.getValue().put(field, StringUtils.isNotBlank(value) ? value : StringUtils.EMPTY); + } + } + } + } + } } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d67d4a82e..4ce0c6b38 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -129,6 +129,8 @@ user.course.enroll=v1/course/enroll html.store.path=/tmp/htmlFiles pdf.store.path=/tmp/pdfFiles +user.report.store.path=/tmp + pdf.draft.template.name=workallocationdraft pdf.published.template.name=workallocationpublished content.default.channelId=0131397178949058560 @@ -263,3 +265,6 @@ latest.courses.alert.search.content.fields=identifier,name,posterImage,duration, latest.courses.alert.email.subject=Check out exciting new courses that launched this week! latest.courses.alert.scheduler.time.gap=100 latest.courses.alert.content.min.limit=1 + +#SB Password for encrypt and decrypt +sb.env.chiper.password=password